2 * Copyright (c) 2018 Red Hat, Inc. and others. All rights reserved.
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
8 package org.opendaylight.aaa.web.osgi;
10 import java.util.ArrayList;
11 import java.util.EventListener;
12 import java.util.List;
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;
41 * {@link WebServer} (and {@link WebContext}) bridge implementation
42 * delegating to Pax Web WebContainer (which extends an OSGi {@link HttpService}).
44 * @author Michael Vorburger.ch - original author
45 * @author Tom Pantelis - added ServiceFactory to solve possible class loading issues in web components
48 public class PaxWebServer implements ServiceFactory<WebServer> {
50 // TODO write an IT (using Pax Exam) which tests this, re-use JettyLauncherTest
52 private static final Logger LOG = LoggerFactory.getLogger(PaxWebServer.class);
54 private final WebContainer paxWeb;
55 private final ServiceRegistration<?> serviceRegistration;
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");
66 serviceRegistration.unregister();
70 WebContainerDTO details = paxWeb.getWebcontainerDTO();
71 if (details.securePort != null && details.securePort > 0) {
72 return "https://" + details.listeningAddresses[0] + ":" + details.securePort;
74 return "http://" + details.listeningAddresses[0] + ":" + details.port;
79 public WebServer getService(final Bundle bundle, final ServiceRegistration<WebServer> registration) {
80 LOG.info("Creating WebServer instance for bundle {}", bundle);
82 final BundleContext bundleContext = bundle.getBundleContext();
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);
89 final WebContainer bundleWebContainer;
90 if (webContainerServiceRef != null) {
91 bundleWebContainer = bundleContext.getService(webContainerServiceRef);
93 bundleWebContainer = null;
96 if (bundleWebContainer == null) {
97 throw new IllegalStateException("WebContainer OSGi service not found using bundle: " + bundle.toString());
100 return new WebServer() {
102 public WebContextRegistration registerWebContext(final WebContext webContext) throws ServletException {
103 return new WebContextImpl(bundleWebContainer, webContext) {
105 public void close() {
109 bundleContext.ungetService(webContainerServiceRef);
110 } catch (IllegalStateException e) {
111 LOG.debug("Error from ungetService", e);
118 public String getBaseURL() {
119 return PaxWebServer.this.getBaseURL();
125 public void ungetService(final Bundle bundle, final ServiceRegistration<WebServer> registration,
126 final WebServer service) {
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<>();
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.
143 this.paxWeb = paxWeb;
144 this.contextPath = webContext.contextPath();
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";
150 HttpContext osgiHttpContext = paxWeb.createDefaultHttpContext(contextID);
151 paxWeb.begin(osgiHttpContext);
153 // The order in which we set things up here matters...
155 // 1. Context parameters - because listeners, filters and servlets could need them
156 paxWeb.setContextParam(new MapDictionary<>(webContext.contextParams()), osgiHttpContext);
158 // 2. Listeners - because they could set up things that filters and servlets need
159 webContext.listeners().forEach(listener -> registerListener(osgiHttpContext, listener));
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()));
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());
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);
178 } catch (NamespaceException e) {
179 throw new ServletException("Error registering resources", e);
182 paxWeb.end(osgiHttpContext);
185 private static String ensurePrependedSlash(final String str) {
186 return !str.startsWith("/") ? "/" + str : str;
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);
199 String[] absolute(final List<String> relatives) {
200 return relatives.stream().map(urlPattern -> contextPath + urlPattern).toArray(String[]::new);
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);
214 void registerListener(final HttpContext osgiHttpContext, final ServletContextListener listener) {
215 paxWeb.registerEventListener(listener, osgiHttpContext);
216 registeredEventListeners.add(listener);
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);
225 for (Filter filter : registeredFilters) {
226 paxWeb.unregisterFilter(filter);
228 for (EventListener eventListener : registeredEventListeners) {
229 paxWeb.unregisterEventListener(eventListener);
231 for (String alias : registeredResources) {
232 paxWeb.unregister(alias);