2dcdecbf0fe7d41e3cf630ef56452b498b2b2a2b
[aaa.git] / web / impl-jetty / src / main / java / org / opendaylight / aaa / web / jetty / JettyWebServer.java
1 /*
2  * Copyright (c) 2018 Red Hat, Inc. and others. All rights reserved.
3  *
4  * This program and the accompanying materials are made available under the
5  * terms of the Eclipse Public License v1.0 which accompanies this distribution,
6  * and is available at http://www.eclipse.org/legal/epl-v10.html
7  */
8 package org.opendaylight.aaa.web.jetty;
9
10 import static com.google.common.base.Preconditions.checkArgument;
11
12 import java.util.EnumSet;
13 import javax.annotation.PostConstruct;
14 import javax.annotation.PreDestroy;
15 import javax.inject.Singleton;
16 import javax.servlet.DispatcherType;
17 import javax.servlet.ServletException;
18 import org.eclipse.jetty.server.Server;
19 import org.eclipse.jetty.server.ServerConnector;
20 import org.eclipse.jetty.server.handler.ContextHandlerCollection;
21 import org.eclipse.jetty.servlet.FilterHolder;
22 import org.eclipse.jetty.servlet.ServletContextHandler;
23 import org.eclipse.jetty.servlet.ServletHolder;
24 import org.eclipse.jetty.util.component.AbstractLifeCycle;
25 import org.opendaylight.aaa.web.WebContext;
26 import org.opendaylight.aaa.web.WebContextRegistration;
27 import org.opendaylight.aaa.web.WebServer;
28 import org.slf4j.Logger;
29 import org.slf4j.LoggerFactory;
30
31 /**
32  * {@link WebServer} (and {@link WebContext}) implementation based on Jetty.
33  *
34  * @author Michael Vorburger.ch
35  */
36 @Singleton
37 public class JettyWebServer implements WebServer {
38
39     private static final Logger LOG = LoggerFactory.getLogger(JettyWebServer.class);
40
41     private static final int HTTP_SERVER_IDLE_TIMEOUT = 30000;
42
43     private int httpPort;
44     private final Server server;
45     private final ServerConnector http;
46     private final ContextHandlerCollection contextHandlerCollection;
47
48     public JettyWebServer() {
49         this(0); // automatically choose free port
50     }
51
52     public JettyWebServer(final int httpPort) {
53         checkArgument(httpPort >= 0, "httpPort must be positive");
54         checkArgument(httpPort < 65536, "httpPort must < 65536");
55
56         this.server = new Server();
57         server.setStopAtShutdown(true);
58
59         http = new ServerConnector(server);
60         http.setHost("localhost");
61         http.setPort(httpPort);
62         http.setIdleTimeout(HTTP_SERVER_IDLE_TIMEOUT);
63         server.addConnector(http);
64
65         this.contextHandlerCollection = new ContextHandlerCollection();
66         server.setHandler(contextHandlerCollection);
67     }
68
69     @Override
70     public String getBaseURL() {
71         if (httpPort == 0) {
72             throw new IllegalStateException("must start() before getBaseURL()");
73         }
74         return "http://localhost:" + httpPort;
75     }
76
77     @PostConstruct
78     public void start() throws Exception {
79         server.start();
80         this.httpPort = http.getLocalPort();
81         LOG.info("Started Jetty-based HTTP web server on port {} ({}).", httpPort, hashCode());
82     }
83
84     @PreDestroy
85     public void stop() throws Exception {
86         LOG.info("Stopping Jetty-based web server...");
87         // NB server.stop() will call stop() on all ServletContextHandler/WebAppContext
88         server.stop();
89         LOG.info("Stopped Jetty-based web server.");
90     }
91
92     @Override
93     public synchronized WebContextRegistration registerWebContext(final WebContext webContext) throws ServletException {
94         String contextPathWithSlashPrefix = webContext.contextPath().startsWith("/")
95                 ? webContext.contextPath() : "/" + webContext.contextPath();
96         ServletContextHandler handler = new ServletContextHandler(contextHandlerCollection, contextPathWithSlashPrefix,
97                 webContext.supportsSessions() ? ServletContextHandler.SESSIONS : ServletContextHandler.NO_SESSIONS);
98
99         // The order in which we do things here must be the same as
100         // the equivalent in org.opendaylight.aaa.web.osgi.PaxWebServer
101
102         // 1. Context parameters - because listeners, filters and servlets could need them
103         webContext.contextParams().entrySet().forEach(entry -> handler.setAttribute(entry.getKey(), entry.getValue()));
104         // also handler.getServletContext().setAttribute(name, value), both seem work
105
106         // 2. Listeners - because they could set up things that filters and servlets need
107         webContext.listeners().forEach(listener -> handler.addEventListener(listener));
108
109         // 3. Filters - because subsequent servlets should already be covered by the filters
110         webContext.filters().forEach(filter -> {
111             FilterHolder filterHolder = new FilterHolder(filter.filter());
112             filterHolder.setInitParameters(filter.initParams());
113             filter.urlPatterns().forEach(
114                 urlPattern -> handler.addFilter(filterHolder, urlPattern, EnumSet.allOf(DispatcherType.class))
115             );
116         });
117
118         // 4. servlets - 'bout time for 'em by now, don't you think? ;)
119         webContext.servlets().forEach(servlet -> {
120             ServletHolder servletHolder = new ServletHolder(servlet.name(), servlet.servlet());
121             servletHolder.setInitParameters(servlet.initParams());
122             servletHolder.setInitOrder(1); // AKA <load-on-startup> 1
123             servlet.urlPatterns().forEach(
124                 urlPattern -> handler.addServlet(servletHolder, urlPattern)
125             );
126         });
127
128         restart(handler);
129
130         return () -> close(handler);
131     }
132
133     @SuppressWarnings("checkstyle:IllegalCatch")
134     private static void restart(final AbstractLifeCycle lifecycle) throws ServletException {
135         try {
136             lifecycle.start();
137         } catch (ServletException | RuntimeException e) {
138             throw e;
139         } catch (Exception e) {
140             throw new ServletException("registerServlet() start failed", e);
141         }
142     }
143
144     @SuppressWarnings("checkstyle:IllegalCatch")
145     private void close(final ServletContextHandler handler) {
146         try {
147             handler.stop();
148         } catch (Exception e) {
149             LOG.error("close() failed", e);
150         } finally {
151             handler.destroy();
152         }
153         contextHandlerCollection.removeHandler(handler);
154     }
155 }