Remove intermediate ServiceFactory
[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.annotation.PreDestroy;
15 import javax.inject.Inject;
16 import javax.inject.Singleton;
17 import javax.servlet.Filter;
18 import javax.servlet.Servlet;
19 import javax.servlet.ServletContextListener;
20 import javax.servlet.ServletException;
21 import org.apache.aries.blueprint.annotation.service.Reference;
22 import org.opendaylight.aaa.web.ResourceDetails;
23 import org.opendaylight.aaa.web.ServletDetails;
24 import org.opendaylight.aaa.web.WebContext;
25 import org.opendaylight.aaa.web.WebContextRegistration;
26 import org.opendaylight.aaa.web.WebServer;
27 import org.ops4j.pax.web.service.WebContainer;
28 import org.ops4j.pax.web.service.WebContainerDTO;
29 import org.osgi.framework.Bundle;
30 import org.osgi.framework.BundleContext;
31 import org.osgi.framework.ServiceFactory;
32 import org.osgi.framework.ServiceReference;
33 import org.osgi.framework.ServiceRegistration;
34 import org.osgi.service.http.HttpContext;
35 import org.osgi.service.http.HttpService;
36 import org.osgi.service.http.NamespaceException;
37 import org.slf4j.Logger;
38 import org.slf4j.LoggerFactory;
39
40 /**
41  * {@link WebServer} (and {@link WebContext}) bridge implementation
42  * delegating to Pax Web WebContainer (which extends an OSGi {@link HttpService}).
43  *
44  * @author Michael Vorburger.ch - original author
45  * @author Tom Pantelis - added ServiceFactory to solve possible class loading issues in web components
46  */
47 @Singleton
48 public class PaxWebServer implements ServiceFactory<WebServer> {
49
50     // TODO write an IT (using Pax Exam) which tests this, re-use JettyLauncherTest
51
52     private static final Logger LOG = LoggerFactory.getLogger(PaxWebServer.class);
53
54     private final WebContainer paxWeb;
55     private final ServiceRegistration<?> serviceRegistration;
56
57     @Inject
58     public PaxWebServer(final @Reference WebContainer paxWebContainer, final BundleContext bundleContext) {
59         this.paxWeb = paxWebContainer;
60         serviceRegistration = bundleContext.registerService(WebServer.class, this, null);
61         LOG.info("PaxWebServer initialized & WebServer service factory registered");
62     }
63
64     @PreDestroy
65     public void close() {
66         serviceRegistration.unregister();
67     }
68
69     String getBaseURL() {
70         WebContainerDTO details = paxWeb.getWebcontainerDTO();
71         if (details.securePort != null && details.securePort > 0) {
72             return "https://" + details.listeningAddresses[0] + ":" + details.securePort;
73         } else {
74             return "http://" + details.listeningAddresses[0] + ":" + details.port;
75         }
76     }
77
78     @Override
79     public WebServer getService(final Bundle bundle, final ServiceRegistration<WebServer> registration) {
80         LOG.info("Creating WebServer instance for bundle {}", bundle);
81
82         final BundleContext bundleContext = bundle.getBundleContext();
83
84         // Get the WebContainer service using the given bundle's context so the WebContainer service instance uses
85         // the bundle's class loader.
86         final ServiceReference<WebContainer> webContainerServiceRef =
87                 bundleContext.getServiceReference(WebContainer.class);
88
89         final WebContainer bundleWebContainer;
90         if (webContainerServiceRef != null) {
91             bundleWebContainer = bundleContext.getService(webContainerServiceRef);
92         } else {
93             bundleWebContainer = null;
94         }
95
96         if (bundleWebContainer == null) {
97             throw new IllegalStateException("WebContainer OSGi service not found using bundle: " + bundle.toString());
98         }
99
100         return new WebServer() {
101             @Override
102             public WebContextRegistration registerWebContext(final WebContext webContext) throws ServletException {
103                 return new WebContextImpl(bundleWebContainer, webContext) {
104                     @Override
105                     public void close() {
106                         super.close();
107
108                         try {
109                             bundleContext.ungetService(webContainerServiceRef);
110                         } catch (IllegalStateException e) {
111                             LOG.debug("Error from ungetService", e);
112                         }
113                     }
114                 };
115             }
116
117             @Override
118             public String getBaseURL() {
119                 return PaxWebServer.this.getBaseURL();
120             }
121         };
122     }
123
124     @Override
125     public void ungetService(final Bundle bundle, final ServiceRegistration<WebServer> registration,
126             final WebServer service) {
127         // no-op
128     }
129
130     private static class WebContextImpl implements WebContextRegistration {
131         private final String contextPath;
132         private final WebContainer paxWeb;
133         private final List<Servlet> registeredServlets = new ArrayList<>();
134         private final List<EventListener> registeredEventListeners = new ArrayList<>();
135         private final List<Filter> registeredFilters = new ArrayList<>();
136         private final List<String> registeredResources = new ArrayList<>();
137
138         WebContextImpl(final WebContainer paxWeb, final WebContext webContext) throws ServletException {
139             // We ignore webContext.supportsSessions() because the OSGi HttpService / Pax Web API
140             // does not seem to support not wanting session support on some web contexts
141             // (it assumes always with session); but other implementation support without.
142
143             this.paxWeb = paxWeb;
144             this.contextPath = webContext.contextPath();
145
146             // NB This is NOT the URL prefix of the context, but the context.id which is
147             // used while registering the HttpContext in the OSGi service registry.
148             String contextID = contextPath + ".id";
149
150             HttpContext osgiHttpContext = paxWeb.createDefaultHttpContext(contextID);
151             paxWeb.begin(osgiHttpContext);
152
153             // The order in which we set things up here matters...
154
155             // 1. Context parameters - because listeners, filters and servlets could need them
156             paxWeb.setContextParam(new MapDictionary<>(webContext.contextParams()), osgiHttpContext);
157
158             // 2. Listeners - because they could set up things that filters and servlets need
159             webContext.listeners().forEach(listener -> registerListener(osgiHttpContext, listener));
160
161             // 3. Filters - because subsequent servlets should already be covered by the filters
162             webContext.filters().forEach(filter ->
163                 registerFilter(osgiHttpContext, filter.urlPatterns(), filter.name(), filter.filter(),
164                         filter.initParams()));
165
166             // 4. servlets - 'bout time for 'em by now, don't you think? ;)
167             for (ServletDetails servlet : webContext.servlets()) {
168                 registerServlet(osgiHttpContext, servlet.urlPatterns(), servlet.name(), servlet.servlet(),
169                         servlet.initParams());
170             }
171
172             try {
173                 for (ResourceDetails resource: webContext.resources()) {
174                     String alias = ensurePrependedSlash(this.contextPath + ensurePrependedSlash(resource.alias()));
175                     paxWeb.registerResources(alias, ensurePrependedSlash(resource.name()), osgiHttpContext);
176                     registeredResources.add(alias);
177                 }
178             } catch (NamespaceException e) {
179                 throw new ServletException("Error registering resources", e);
180             }
181
182             paxWeb.end(osgiHttpContext);
183         }
184
185         private static String ensurePrependedSlash(final String str) {
186             return !str.startsWith("/") ? "/" + str : str;
187         }
188
189         void registerFilter(final HttpContext osgiHttpContext, final List<String> urlPatterns, final String name,
190                 final Filter filter, final Map<String, String> params) {
191             boolean asyncSupported = false;
192             String[] absUrlPatterns = absolute(urlPatterns);
193             LOG.info("Registering Filter for aliases {}: {}", absUrlPatterns, filter);
194             paxWeb.registerFilter(filter, absUrlPatterns, new String[] { name }, new MapDictionary<>(params),
195                     asyncSupported, osgiHttpContext);
196             registeredFilters.add(filter);
197         }
198
199         String[] absolute(final List<String> relatives) {
200             return relatives.stream().map(urlPattern -> contextPath + urlPattern).toArray(String[]::new);
201         }
202
203         void registerServlet(final HttpContext osgiHttpContext, final List<String> urlPatterns, final String name,
204                 final Servlet servlet, final Map<String, String> params) throws ServletException {
205             int loadOnStartup = 1;
206             boolean asyncSupported = false;
207             String[] absUrlPatterns = absolute(urlPatterns);
208             LOG.info("Registering Servlet for aliases {}: {}", absUrlPatterns, servlet);
209             paxWeb.registerServlet(servlet, name, absUrlPatterns, new MapDictionary<>(params), loadOnStartup,
210                     asyncSupported, osgiHttpContext);
211             registeredServlets.add(servlet);
212         }
213
214         void registerListener(final HttpContext osgiHttpContext, final ServletContextListener listener) {
215             paxWeb.registerEventListener(listener, osgiHttpContext);
216             registeredEventListeners.add(listener);
217         }
218
219         @Override
220         public void close() {
221             // The order is relevant here.. Servlets first, then Filters, Listeners last; this is the inverse of above
222             for (Servlet registeredServlet : registeredServlets) {
223                 paxWeb.unregisterServlet(registeredServlet);
224             }
225             for (Filter filter : registeredFilters) {
226                 paxWeb.unregisterFilter(filter);
227             }
228             for (EventListener eventListener : registeredEventListeners) {
229                 paxWeb.unregisterEventListener(eventListener);
230             }
231             for (String alias : registeredResources) {
232                 paxWeb.unregister(alias);
233             }
234         }
235     }
236 }