add web API implementation for OSGi environment, based on Pax Web
[aaa.git] / web / impl-osgi / src / main / java / org / opendaylight / aaa / web / osgi / PaxWebServer.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.osgi;
9
10 import java.util.ArrayList;
11 import java.util.EventListener;
12 import java.util.List;
13 import java.util.Map;
14 import javax.inject.Inject;
15 import javax.servlet.Filter;
16 import javax.servlet.Servlet;
17 import javax.servlet.ServletContextListener;
18 import javax.servlet.ServletException;
19 import org.opendaylight.aaa.web.ServletDetails;
20 import org.opendaylight.aaa.web.WebContext;
21 import org.opendaylight.aaa.web.WebContextRegistration;
22 import org.opendaylight.aaa.web.WebServer;
23 import org.ops4j.pax.cdi.api.OsgiService;
24 import org.ops4j.pax.web.service.WebContainer;
25 import org.ops4j.pax.web.service.WebContainerDTO;
26 import org.osgi.service.http.HttpContext;
27 import org.osgi.service.http.HttpService;
28 import org.slf4j.Logger;
29 import org.slf4j.LoggerFactory;
30
31 /**
32  * {@link WebServer} (and {@link WebContext}) bridge implementation
33  * delegating to Pax Web WebContainer (which extends an OSGi {@link HttpService}).
34  *
35  * @author Michael Vorburger.ch
36  */
37 // This is a utility class and cannot be a @Singleton @OsgiServiceProvider
38 // (because Pax Web handles class loading relative to its calling bundle)
39 public class PaxWebServer implements WebServer {
40
41     // TODO write an IT (using Pax Exam) which tests this, re-use JettyLauncherTest
42
43     private static final Logger LOG = LoggerFactory.getLogger(PaxWebServer.class);
44
45     private final WebContainer paxWeb;
46
47     @Inject
48     public PaxWebServer(@OsgiService WebContainer paxWebContainer) {
49         this.paxWeb = paxWebContainer;
50     }
51
52     @Override
53     public String getBaseURL() {
54         WebContainerDTO details = paxWeb.getWebcontainerDTO();
55         return "http://" + details.listeningAddresses[0] + ":" + details.port;
56     }
57
58     @Override
59     public WebContextRegistration registerWebContext(WebContext webContext) throws ServletException {
60         return new WebContextImpl(webContext);
61     }
62
63     private class WebContextImpl implements WebContextRegistration {
64
65         private final String contextPath;
66
67         private final List<Servlet> registeredServlets = new ArrayList<>();
68         private final List<EventListener> registeredEventListeners = new ArrayList<>();
69         private final List<Filter> registeredFilters = new ArrayList<>();
70
71         WebContextImpl(WebContext webContext) throws ServletException {
72             // We ignore webContext.supportsSessions() because the OSGi HttpService / Pax Web API
73             // does not seem to support not wanting session support on some web contexts
74             // (it assumes always with session); but other implementation support without.
75
76             this.contextPath = webContext.contextPath();
77
78             // NB This is NOT the URL prefix of the context, but the context.id which is
79             // used while registering the HttpContext in the OSGi service registry.
80             String contextID = contextPath + ".id";
81
82             HttpContext osgiHttpContext = paxWeb.createDefaultHttpContext(contextID);
83             paxWeb.begin(osgiHttpContext);
84
85             // The order in which we set things up here matters...
86
87             // 1. Context parameters - because listeners, filters and servlets could need them
88             paxWeb.setContextParam(new MapDictionary<>(webContext.contextParams()), osgiHttpContext);
89
90             // 2. Listeners - because they could set up things that filters and servlets need
91             webContext.listeners().forEach(listener -> registerListener(osgiHttpContext, listener));
92
93             // 3. Filters - because subsequent servlets should already be covered by the filters
94             webContext.filters().forEach(filter ->
95                 registerFilter(osgiHttpContext, filter.urlPatterns(), filter.name(), filter.filter(),
96                         filter.initParams()));
97
98             // 4. servlets - 'bout time for 'em by now, don't you think? ;)
99             for (ServletDetails servlet : webContext.servlets()) {
100                 registerServlet(osgiHttpContext, servlet.urlPatterns(), servlet.name(), servlet.servlet(),
101                         servlet.initParams());
102             }
103
104             paxWeb.end(osgiHttpContext);
105         }
106
107         void registerFilter(HttpContext osgiHttpContext, List<String> urlPatterns, String name, Filter filter,
108                 Map<String, String> params) {
109             boolean asyncSupported = false;
110             String[] absUrlPatterns = absolute(urlPatterns);
111             LOG.info("Registering Filter for aliases {}: {}", absUrlPatterns, filter);
112             paxWeb.registerFilter(filter, absUrlPatterns, new String[] { name }, new MapDictionary<>(params),
113                     asyncSupported, osgiHttpContext);
114             registeredFilters.add(filter);
115         }
116
117         String[] absolute(List<String> relatives) {
118             return relatives.stream().map(urlPattern -> contextPath + urlPattern).toArray(String[]::new);
119         }
120
121         void registerServlet(HttpContext osgiHttpContext, List<String> urlPatterns, String name, Servlet servlet,
122                 Map<String, String> params) throws ServletException {
123             int loadOnStartup = 1;
124             boolean asyncSupported = false;
125             String[] absUrlPatterns = absolute(urlPatterns);
126             LOG.info("Registering Servlet for aliases {}: {}", absUrlPatterns, servlet);
127             paxWeb.registerServlet(servlet, name, absUrlPatterns, new MapDictionary<>(params), loadOnStartup,
128                     asyncSupported, osgiHttpContext);
129             registeredServlets.add(servlet);
130         }
131
132         void registerListener(HttpContext osgiHttpContext, ServletContextListener listener) {
133             paxWeb.registerEventListener(listener, osgiHttpContext);
134             registeredEventListeners.add(listener);
135         }
136
137         @Override
138         public void close() {
139             // The order is relevant here.. Servlets first, then Filters, Listeners last; this is the inverse of above
140             for (Servlet registeredServlet : registeredServlets) {
141                 paxWeb.unregisterServlet(registeredServlet);
142             }
143             for (Filter filter : registeredFilters) {
144                 paxWeb.unregisterFilter(filter);
145             }
146             for (EventListener eventListener : registeredEventListeners) {
147                 paxWeb.unregisterEventListener(eventListener);
148             }
149         }
150
151     }
152
153 }