137855c8740ab55457a688d87eaa1f635a516c56
[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.WebServer;
27 import org.opendaylight.yangtools.concepts.Registration;
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         // automatically choose free port
50         this(0);
51     }
52
53     public JettyWebServer(final int httpPort) {
54         checkArgument(httpPort >= 0, "httpPort must be positive");
55         checkArgument(httpPort < 65536, "httpPort must < 65536");
56
57         server = new Server();
58         server.setStopAtShutdown(true);
59
60         http = new ServerConnector(server);
61         http.setHost("localhost");
62         http.setPort(httpPort);
63         http.setIdleTimeout(HTTP_SERVER_IDLE_TIMEOUT);
64         server.addConnector(http);
65
66         contextHandlerCollection = new ContextHandlerCollection();
67         server.setHandler(contextHandlerCollection);
68     }
69
70     @Override
71     public String getBaseURL() {
72         if (httpPort == 0) {
73             throw new IllegalStateException("must start() before getBaseURL()");
74         }
75         return "http://localhost:" + httpPort;
76     }
77
78     @PostConstruct
79     public void start() throws Exception {
80         server.start();
81         httpPort = http.getLocalPort();
82         LOG.info("Started Jetty-based HTTP web server on port {} ({}).", httpPort, hashCode());
83     }
84
85     @PreDestroy
86     public void stop() throws Exception {
87         LOG.info("Stopping Jetty-based web server...");
88         // NB server.stop() will call stop() on all ServletContextHandler/WebAppContext
89         server.stop();
90         LOG.info("Stopped Jetty-based web server.");
91     }
92
93     @Override
94     public synchronized Registration registerWebContext(final WebContext webContext) throws ServletException {
95         String contextPathWithSlashPrefix = webContext.contextPath().startsWith("/")
96                 ? webContext.contextPath() : "/" + webContext.contextPath();
97         ServletContextHandler handler = new ServletContextHandler(contextHandlerCollection, contextPathWithSlashPrefix,
98                 webContext.supportsSessions() ? ServletContextHandler.SESSIONS : ServletContextHandler.NO_SESSIONS);
99
100         // The order in which we do things here must be the same as
101         // the equivalent in org.opendaylight.aaa.web.osgi.PaxWebServer
102
103         // 1. Context parameters - because listeners, filters and servlets could need them
104         webContext.contextParams().entrySet().forEach(entry -> handler.setAttribute(entry.getKey(), entry.getValue()));
105         // also handler.getServletContext().setAttribute(name, value), both seem work
106
107         // 2. Listeners - because they could set up things that filters and servlets need
108         webContext.listeners().forEach(listener -> handler.addEventListener(listener));
109
110         // 3. Filters - because subsequent servlets should already be covered by the filters
111         webContext.filters().forEach(filter -> {
112             FilterHolder filterHolder = new FilterHolder(filter.filter());
113             filterHolder.setInitParameters(filter.initParams());
114             filter.urlPatterns().forEach(
115                 urlPattern -> handler.addFilter(filterHolder, urlPattern, EnumSet.allOf(DispatcherType.class))
116             );
117         });
118
119         // 4. servlets - 'bout time for 'em by now, don't you think? ;)
120         webContext.servlets().forEach(servlet -> {
121             ServletHolder servletHolder = new ServletHolder(servlet.name(), servlet.servlet());
122             servletHolder.setInitParameters(servlet.initParams());
123             servletHolder.setAsyncSupported(servlet.getAsyncSupported());
124             // AKA <load-on-startup> 1
125             servletHolder.setInitOrder(1);
126             servlet.urlPatterns().forEach(
127                 urlPattern -> handler.addServlet(servletHolder, urlPattern)
128             );
129         });
130
131         restart(handler);
132
133         return () -> close(handler);
134     }
135
136     @SuppressWarnings("checkstyle:IllegalCatch")
137     private static void restart(final AbstractLifeCycle lifecycle) throws ServletException {
138         try {
139             lifecycle.start();
140         } catch (ServletException | RuntimeException e) {
141             throw e;
142         } catch (Exception e) {
143             throw new ServletException("registerServlet() start failed", e);
144         }
145     }
146
147     @SuppressWarnings("checkstyle:IllegalCatch")
148     private void close(final ServletContextHandler handler) {
149         try {
150             handler.stop();
151         } catch (Exception e) {
152             LOG.error("close() failed", e);
153         } finally {
154             handler.destroy();
155         }
156         contextHandlerCollection.removeHandler(handler);
157     }
158 }