add full implementation in web-jetty-impl 70/70870/5
authorMichael Vorburger <vorburger@redhat.com>
Thu, 12 Apr 2018 16:06:17 +0000 (18:06 +0200)
committerMichael Vorburger <vorburger@redhat.com>
Tue, 17 Apr 2018 16:26:02 +0000 (18:26 +0200)
Change-Id: I649336bcaf51f683e52284cc549332c4c1815836
Signed-off-by: Michael Vorburger <vorburger@redhat.com>
web/impl-jetty/pom.xml
web/impl-jetty/src/main/java/org/opendaylight/aaa/web/jetty/JettyWebServer.java
web/impl-jetty/src/test/java/org/opendaylight/aaa/web/jetty/test/JettyWebServerTest.java
web/impl-jetty/src/test/java/org/opendaylight/aaa/web/test/AbstractWebServerTest.java
web/impl-jetty/src/test/java/org/opendaylight/aaa/web/test/TestListener.java

index 3615bd7634174392ee4da4827f8192b89eb788d9..38cade731dec83bc96e78dfe290f8c523433c13e 100644 (file)
   <name>ODL :: aaa :: ${project.artifactId}</name>
   <packaging>bundle</packaging>
 
+  <properties>
+    <!-- Find duplicates on Classpath, to avoid Jetty version conflicts -->
+    <duplicate-finder.skip>false</duplicate-finder.skip>
+  </properties>
+
   <dependencies>
     <dependency>
       <groupId>${project.groupId}</groupId>
       <version>${project.version}</version>
     </dependency>
 
+    <dependency>
+      <groupId>org.eclipse.jetty</groupId>
+      <artifactId>jetty-webapp</artifactId>
+    </dependency>
+
     <dependency>
       <groupId>com.google.guava</groupId>
       <artifactId>guava</artifactId>
@@ -44,6 +54,5 @@
       <groupId>com.google.truth</groupId>
       <artifactId>truth</artifactId>
     </dependency>
-
   </dependencies>
 </project>
index 3e32c020d70b066d5a2ddde864a8827207c712c4..577c51a6b84de72e420b490a3e1f6fe80f133c6c 100644 (file)
@@ -7,11 +7,26 @@
  */
 package org.opendaylight.aaa.web.jetty;
 
+import static com.google.common.base.Preconditions.checkArgument;
+
+import java.util.EnumSet;
+import javax.annotation.PostConstruct;
+import javax.annotation.PreDestroy;
 import javax.inject.Singleton;
+import javax.servlet.DispatcherType;
 import javax.servlet.ServletException;
+import org.eclipse.jetty.server.Server;
+import org.eclipse.jetty.server.ServerConnector;
+import org.eclipse.jetty.server.handler.ContextHandlerCollection;
+import org.eclipse.jetty.servlet.FilterHolder;
+import org.eclipse.jetty.servlet.ServletContextHandler;
+import org.eclipse.jetty.servlet.ServletHolder;
+import org.eclipse.jetty.util.component.AbstractLifeCycle;
 import org.opendaylight.aaa.web.WebContext;
 import org.opendaylight.aaa.web.WebContextRegistration;
 import org.opendaylight.aaa.web.WebServer;
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
 
 /**
  * {@link WebServer} (and {@link WebContext}) implementation based on Jetty.
@@ -19,20 +34,113 @@ import org.opendaylight.aaa.web.WebServer;
  * @author Michael Vorburger.ch
  */
 @Singleton
-// @SuppressWarnings("checkstyle:IllegalCatch") // Jetty LifeCycle start() and stop() throws Exception
+@SuppressWarnings("checkstyle:IllegalCatch") // Jetty LifeCycle start() and stop() throws Exception
 public class JettyWebServer implements WebServer {
 
-    public void stop() {
+    private static final Logger LOG = LoggerFactory.getLogger(JettyWebServer.class);
+
+    private static final int HTTP_SERVER_IDLE_TIMEOUT = 30000;
+
+    private final int httpPort;
+    private final Server server;
+    private final ContextHandlerCollection contextHandlerCollection;
+
+    public JettyWebServer(int httpPort) {
+        checkArgument(httpPort > 0, "httpPort must be positive");
+        checkArgument(httpPort < 65536, "httpPort must < 65536");
+
+        this.httpPort = httpPort;
+        this.server = new Server();
+        server.setStopAtShutdown(true);
+
+        ServerConnector http = new ServerConnector(server);
+        http.setHost("localhost");
+        http.setPort(httpPort);
+        http.setIdleTimeout(HTTP_SERVER_IDLE_TIMEOUT);
+        server.addConnector(http);
+
+        this.contextHandlerCollection = new ContextHandlerCollection();
+        server.setHandler(contextHandlerCollection);
     }
 
     @Override
-    public WebContextRegistration registerWebContext(WebContext webContext) throws ServletException {
-        throw new UnsupportedOperationException("TODO Implement!");
+    public String getBaseURL() {
+        return "http://localhost:" + httpPort;
+    }
+
+    @PostConstruct
+    @SuppressWarnings("checkstyle:IllegalThrows") // Jetty WebAppContext.getUnavailableException() throws Throwable
+    public void start() throws Throwable {
+        server.start();
+        LOG.info("Started Jetty-based HTTP web server on port {} ({}).", httpPort, hashCode());
+    }
+
+    @PreDestroy
+    public void stop() throws Exception {
+        LOG.info("Stopping Jetty-based web server...");
+        // NB server.stop() will call stop() on all ServletContextHandler/WebAppContext
+        server.stop();
+        LOG.info("Stopped Jetty-based web server.");
     }
 
     @Override
-    public String getBaseURL() {
-        throw new UnsupportedOperationException("TODO Implement!");
+    public synchronized WebContextRegistration registerWebContext(WebContext webContext) throws ServletException {
+        ServletContextHandler handler = new ServletContextHandler(contextHandlerCollection, webContext.contextPath(),
+                webContext.supportsSessions() ? ServletContextHandler.SESSIONS : ServletContextHandler.NO_SESSIONS);
+
+        // The order in which we do things here must be the same as
+        // the equivalent in org.opendaylight.aaa.web.osgi.PaxWebServer
+
+        // 1. Context parameters - because listeners, filters and servlets could need them
+        webContext.contextParams().entrySet().forEach(entry -> handler.setAttribute(entry.getKey(), entry.getValue()));
+        // also handler.getServletContext().setAttribute(name, value), both seem work
+
+        // 2. Listeners - because they could set up things that filters and servlets need
+        webContext.listeners().forEach(listener -> handler.addEventListener(listener));
+
+        // 3. Filters - because subsequent servlets should already be covered by the filters
+        webContext.filters().forEach(filter -> {
+            FilterHolder filterHolder = new FilterHolder(filter.filter());
+            filterHolder.setInitParameters(filter.initParams());
+            filter.urlPatterns().forEach(
+                urlPattern -> handler.addFilter(filterHolder, urlPattern, EnumSet.allOf(DispatcherType.class))
+            );
+        });
+
+        // 4. servlets - 'bout time for 'em by now, don't you think? ;)
+        webContext.servlets().forEach(servlet -> {
+            ServletHolder servletHolder = new ServletHolder(servlet.name(), servlet.servlet());
+            servletHolder.setInitParameters(servlet.initParams());
+            servletHolder.setInitOrder(1); // AKA <load-on-startup> 1
+            servlet.urlPatterns().forEach(
+                urlPattern -> handler.addServlet(servletHolder, urlPattern)
+            );
+        });
+
+        restart(handler);
+
+        return () -> close(handler);
     }
 
+    private void restart(AbstractLifeCycle lifecycle) throws ServletException {
+        try {
+            lifecycle.start();
+        } catch (Exception e) {
+            if (e instanceof ServletException) {
+                throw (ServletException) e;
+            } else {
+                throw new ServletException("registerServlet() start failed", e);
+            }
+        }
+    }
+
+    private void close(ServletContextHandler handler) {
+        try {
+            handler.stop();
+            handler.destroy();
+        } catch (Exception e) {
+            LOG.error("close() failed", e);
+        }
+        contextHandlerCollection.removeHandler(handler);
+    }
 }
index 7d4176e2049a8b7d9d14f1323c8f072bf404d6d1..13caf38c2402f35d3b0db1e734de75a396817a98 100644 (file)
@@ -23,8 +23,10 @@ public class JettyWebServerTest extends AbstractWebServerTest {
     private JettyWebServer webServer;
 
     @Before
-    public void beforeTest() {
-        webServer = new JettyWebServer();
+    @SuppressWarnings("checkstyle:IllegalThrows") // Jetty throws Throwable
+    public void beforeTest() throws Throwable {
+        webServer = new JettyWebServer(8282);
+        webServer.start();
     }
 
     @Override
@@ -33,7 +35,7 @@ public class JettyWebServerTest extends AbstractWebServerTest {
     }
 
     @After
-    public void afterTest() {
+    public void afterTest() throws Exception {
         webServer.stop();
     }
 }
index caf9c0552f556ef86a9797be1fefcc0ec1c5a59d..ec1efa4e163ef163cac550abd5f356b658d35cf0 100644 (file)
@@ -17,7 +17,6 @@ import java.io.InputStreamReader;
 import java.net.URL;
 import java.net.URLConnection;
 import javax.servlet.ServletException;
-import org.junit.Ignore;
 import org.junit.Test;
 import org.opendaylight.aaa.web.FilterDetails;
 import org.opendaylight.aaa.web.ServletDetails;
@@ -37,9 +36,8 @@ public abstract class AbstractWebServerTest {
     protected abstract WebServer getWebServer();
 
     @Test
-    @Ignore
     public void testAddAfterStart() throws ServletException, IOException {
-        WebContextBuilder webContextBuilder = WebContext.builder().contextPath("test1");
+        WebContextBuilder webContextBuilder = WebContext.builder().contextPath("/test1");
         webContextBuilder.addServlet(
                 ServletDetails.builder().addUrlPattern("/*").name("Test").servlet(new TestServlet()).build());
         WebContextRegistration webContextRegistration = getWebServer().registerWebContext(webContextBuilder.build());
@@ -48,10 +46,9 @@ public abstract class AbstractWebServerTest {
     }
 
     @Test
-    @Ignore
     public void testAddFilter() throws Exception {
         TestFilter testFilter = new TestFilter();
-        WebContextBuilder webContextBuilder = WebContext.builder().contextPath("testingFilters");
+        WebContextBuilder webContextBuilder = WebContext.builder().contextPath("/testingFilters");
         webContextBuilder
             .putContextParam("testParam1", "avalue")
             .addFilter(FilterDetails.builder().addUrlPattern("/*").name("Test").filter(testFilter).build());
@@ -61,15 +58,15 @@ public abstract class AbstractWebServerTest {
     }
 
     @Test
-    @Ignore
     public void testRegisterListener() throws Exception {
-        WebContextBuilder webContextBuilder = WebContext.builder().contextPath("testingListener");
+        WebContextBuilder webContextBuilder = WebContext.builder().contextPath("/testingListener");
         TestListener testListener = new TestListener();
         webContextBuilder.addListener(testListener);
         assertThat(testListener.isInitialized).isFalse();
         WebContextRegistration webContextRegistration = getWebServer().registerWebContext(webContextBuilder.build());
         assertThat(testListener.isInitialized).isTrue();
         webContextRegistration.close();
+        assertThat(testListener.isInitialized).isFalse();
     }
 
     static void checkTestServlet(String urlPrefix) throws IOException {
index a4791be8f19383cb444f60bc7959e07d34f3cd5a..be507ca4446a0b19c460a96dc1de153d5b39a3b9 100644 (file)
@@ -21,6 +21,7 @@ public class TestListener implements ServletContextListener {
 
     @Override
     public void contextDestroyed(ServletContextEvent event) {
+        isInitialized = false;
     }
 
 }