+package jalview.httpserver;
+
+import java.net.BindException;
+import java.net.URI;
+import java.util.Collections;
+import java.util.HashMap;
+import java.util.Map;
+
+import javax.servlet.http.HttpServletRequest;
+import javax.servlet.http.HttpServletResponse;
+
+import org.eclipse.jetty.server.Handler;
+import org.eclipse.jetty.server.Server;
+import org.eclipse.jetty.server.handler.ContextHandler;
+import org.eclipse.jetty.server.handler.HandlerCollection;
+import org.eclipse.jetty.util.thread.QueuedThreadPool;
+
+/**
+ * An HttpServer built on Jetty
+ *
+ * @author gmcarstairs
+ * @see http://eclipse.org/jetty/documentation/current/embedding-jetty.html
+ */
+public class HttpServer
+{
+ private static final String JALVIEW_PATH = "/jalview";
+
+ /*
+ * Singleton instance of this server
+ */
+ private static HttpServer instance;
+
+ /*
+ * The Http server
+ */
+ private Server server;
+
+ /*
+ * Registered handlers for context paths
+ */
+ private HandlerCollection contextHandlers;
+
+ /*
+ * Lookup of ContextHandler by its wrapped handler
+ */
+ Map<Handler, ContextHandler> myHandlers = new HashMap<Handler, ContextHandler>();
+
+ /*
+ * The context root for the server
+ */
+ private URI contextRoot;
+
+ /**
+ * Returns the singleton instance of this class.
+ *
+ * @return
+ * @throws BindException
+ */
+ public static HttpServer getInstance() throws BindException
+ {
+ synchronized (HttpServer.class)
+ {
+ if (instance == null) {
+ instance = new HttpServer();
+ }
+ return instance;
+ }
+ }
+
+ /**
+ * Private constructor to enforce use of singleton
+ *
+ * @throws BindException
+ * if no free port can be assigned
+ */
+ private HttpServer() throws BindException
+ {
+ startServer();
+ }
+
+ /**
+ * Start the http server with a default thread pool size of 1
+ *
+ * @throws BindException
+ */
+ private void startServer() throws BindException
+ {
+ try
+ {
+ // problem: how to both limit thread pool size and
+ // pick a random free port - two alternative constructors
+ QueuedThreadPool tp = new QueuedThreadPool(1, 1);
+ // server = new Server(tp); // ? fails with URI null
+ server = new Server(0); // pick a free port number
+
+ /*
+ * HttpServer shuts down with Jalview process
+ */
+ server.setStopAtShutdown(true);
+
+ /*
+ * Create a mutable set of handlers (can add handlers while the server is
+ * running)
+ */
+ // TODO how to combine this with context root "/jalview"
+ contextHandlers = new HandlerCollection(true);
+ server.setHandler(contextHandlers);
+
+ server.start();
+ contextRoot = server.getURI();
+ System.out.println("Jalview endpoint " + contextRoot);
+ } catch (Exception e)
+ {
+ System.err.println("Error trying to start HttpServer: "
+ + e.getMessage());
+ try
+ {
+ server.stop();
+ } catch (Exception e1)
+ {
+ e1.printStackTrace();
+ }
+ }
+ if (server == null)
+ {
+ throw new BindException("HttpServer failed to allocate a port");
+ }
+ }
+
+ /**
+ * Returns the URI on which we are listening
+ *
+ * @return
+ */
+ public URI getUri()
+ {
+ return server == null ? null : server.getURI();
+ }
+
+ /**
+ * For debug - write HTTP request details to stdout
+ *
+ * @param request
+ * @param response
+ */
+ protected void dumpRequest(HttpServletRequest request,
+ HttpServletResponse response)
+ {
+ for (String hdr : Collections.list(request.getHeaderNames()))
+ {
+ for (String val : Collections.list(request.getHeaders(hdr)))
+ {
+ System.out.println(hdr + ": " + val);
+ }
+ }
+ for (String param : Collections.list(request.getParameterNames()))
+ {
+ for (String val : request.getParameterValues(param))
+ {
+ System.out.println(param + "=" + val);
+ }
+ }
+ }
+
+ /**
+ * Stop the Http server.
+ */
+ public void stopServer()
+ {
+ if (server != null)
+ {
+ if (server.isStarted())
+ {
+ try
+ {
+ server.stop();
+ } catch (Exception e)
+ {
+ System.err.println("Error stopping Http Server on "
+ + server.getURI() + ": " + e.getMessage());
+ }
+ }
+ }
+ }
+
+ /**
+ * Register a handler for the given path and returns its URI
+ *
+ * @param path
+ * a path below the context root (without leading or trailing
+ * separator)
+ * @param handler
+ * @return
+ */
+ public String registerHandler(String path, AbstractRequestHandler handler)
+ {
+ // http://stackoverflow.com/questions/20043097/jetty-9-embedded-adding-handlers-during-runtime
+ ContextHandler ch = new ContextHandler();
+ ch.setContextPath("/" + path);
+ ch.setResourceBase(".");
+ ch.setClassLoader(Thread.currentThread()
+ .getContextClassLoader());
+ ch.setHandler(handler);
+
+ /*
+ * Remember the association so we can remove it later
+ */
+ this.myHandlers.put(handler, ch);
+
+ /*
+ * A handler added to a running server must be started explicitly
+ */
+ contextHandlers.addHandler(ch);
+ try
+ {
+ ch.start();
+ } catch (Exception e)
+ {
+ System.err.println("Error starting handler for " + path + ": "
+ + e.getMessage());
+ }
+
+ return this.contextRoot + ch.getContextPath().substring(1);
+ }
+
+ /**
+ * Removes the handler from the server; more precisely, remove the
+ * ContextHandler wrapping the specified handler
+ *
+ * @param handler
+ */
+ public void removeHandler(Handler handler)
+ {
+ /*
+ * Have to use this cached lookup table since there is no method
+ * ContextHandler.getHandler()
+ */
+ ContextHandler ch = myHandlers.get(handler);
+ if (ch != null)
+ {
+ contextHandlers.removeHandler(ch);
+ myHandlers.remove(handler);
+ }
+ }
+}