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 static com.google.common.base.Verify.verifyNotNull;
12 import java.util.ArrayList;
13 import java.util.Arrays;
14 import java.util.EventListener;
15 import java.util.List;
17 import javax.servlet.Filter;
18 import javax.servlet.Servlet;
19 import javax.servlet.ServletContextListener;
20 import javax.servlet.ServletException;
21 import org.opendaylight.aaa.web.FilterDetails;
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.WebServer;
26 import org.opendaylight.yangtools.concepts.AbstractRegistration;
27 import org.opendaylight.yangtools.concepts.Registration;
28 import org.ops4j.pax.web.service.WebContainer;
29 import org.ops4j.pax.web.service.WebContainerDTO;
30 import org.osgi.framework.Bundle;
31 import org.osgi.framework.BundleContext;
32 import org.osgi.framework.ServiceReference;
33 import org.osgi.service.component.ComponentContext;
34 import org.osgi.service.component.annotations.Activate;
35 import org.osgi.service.component.annotations.Component;
36 import org.osgi.service.component.annotations.Deactivate;
37 import org.osgi.service.component.annotations.Reference;
38 import org.osgi.service.component.annotations.ServiceScope;
39 import org.osgi.service.http.HttpContext;
40 import org.osgi.service.http.HttpService;
41 import org.osgi.service.http.NamespaceException;
42 import org.slf4j.Logger;
43 import org.slf4j.LoggerFactory;
46 * {@link WebServer} (and {@link WebContext}) bridge implementation
47 * delegating to Pax Web WebContainer (which extends an OSGi {@link HttpService}).
49 * @author Michael Vorburger.ch - original author
50 * @author Tom Pantelis - added ServiceFactory to solve possible class loading issues in web components
51 * @author Robert Varga - reworked to use OSGi DS, which cuts the implementation down to bare bones.
53 // FIXME: this really acts as an extender (note how we lookup in the context of target bundle) and should really be
54 // eliminated in favor of such
55 // FIXME: even if not, OSGi R7 is changing the picture and should allow us to work without this crud
56 // TODO write an IT (using Pax Exam) which tests this, re-use JettyLauncherTest
57 @Component(scope = ServiceScope.BUNDLE)
58 public final class PaxWebServer implements WebServer {
59 private static final Logger LOG = LoggerFactory.getLogger(PaxWebServer.class);
61 // Global reference, acts as an activation guard
63 WebContainer global = null;
65 private ServiceReference<WebContainer> ref;
66 private WebContainer local;
69 public String getBaseURL() {
70 final WebContainerDTO details = local.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 Registration registerWebContext(final WebContext webContext) throws ServletException {
80 return new WebContextImpl(local, webContext);
84 void activate(final ComponentContext componentContext) {
85 final Bundle bundle = componentContext.getUsingBundle();
86 final BundleContext bundleContext = bundle.getBundleContext();
88 ref = verifyNotNull(bundle.getBundleContext().getServiceReference(WebContainer.class),
89 "Failed to locate WebContext from %s", bundle);
90 local = verifyNotNull(bundleContext.getService(ref), "Failed to get WebContext in %s", bundle);
91 LOG.info("Activated WebServer instance for {}", bundleContext);
95 void deactivate(final ComponentContext componentContext) {
96 final Bundle bundle = componentContext.getUsingBundle();
97 final BundleContext bundleContext = bundle.getBundleContext();
99 bundleContext.ungetService(ref);
101 LOG.info("Deactivated WebServer instance for {}", bundle);
104 private static class WebContextImpl extends AbstractRegistration {
105 private final String contextPath;
106 private final WebContainer paxWeb;
107 private final List<Servlet> registeredServlets = new ArrayList<>();
108 private final List<EventListener> registeredEventListeners = new ArrayList<>();
109 private final List<Filter> registeredFilters = new ArrayList<>();
110 private final List<String> registeredResources = new ArrayList<>();
112 WebContextImpl(final WebContainer paxWeb, final WebContext webContext) throws ServletException {
113 // We ignore webContext.supportsSessions() because the OSGi HttpService / Pax Web API
114 // does not seem to support not wanting session support on some web contexts
115 // (it assumes always with session); but other implementation support without.
117 this.paxWeb = paxWeb;
118 contextPath = webContext.contextPath();
120 // NB This is NOT the URL prefix of the context, but the context.id which is
121 // used while registering the HttpContext in the OSGi service registry.
122 String contextID = contextPath + ".id";
124 HttpContext osgiHttpContext = paxWeb.createDefaultHttpContext(contextID);
125 paxWeb.begin(osgiHttpContext);
127 // The order in which we set things up here matters...
129 // 1. Context parameters - because listeners, filters and servlets could need them
130 paxWeb.setContextParam(new MapDictionary<>(webContext.contextParams()), osgiHttpContext);
132 // 2. Listeners - because they could set up things that filters and servlets need
133 for (ServletContextListener listener : webContext.listeners()) {
134 registerListener(osgiHttpContext, listener);
137 // 3. Filters - because subsequent servlets should already be covered by the filters
138 for (FilterDetails filter : webContext.filters()) {
139 registerFilter(osgiHttpContext, filter.urlPatterns(), filter.name(), filter.filter(),
140 filter.initParams(), filter.getAsyncSupported());
143 // 4. servlets - 'bout time for 'em by now, don't you think? ;)
144 for (ServletDetails servlet : webContext.servlets()) {
145 registerServlet(osgiHttpContext, servlet.urlPatterns(), servlet.name(), servlet.servlet(),
146 servlet.initParams(), servlet.getAsyncSupported());
150 for (ResourceDetails resource: webContext.resources()) {
151 String alias = ensurePrependedSlash(contextPath + ensurePrependedSlash(resource.alias()));
152 paxWeb.registerResources(alias, ensurePrependedSlash(resource.name()), osgiHttpContext);
153 registeredResources.add(alias);
155 } catch (NamespaceException e) {
156 throw new ServletException("Error registering resources", e);
159 paxWeb.end(osgiHttpContext);
162 private static String ensurePrependedSlash(final String str) {
163 return str.startsWith("/") ? str : "/" + str;
166 private void registerFilter(final HttpContext osgiHttpContext, final List<String> urlPatterns,
167 final String name, final Filter filter, final Map<String, String> params,
168 final Boolean asyncSupported) {
169 final String[] absUrlPatterns = absolute(urlPatterns);
170 LOG.info("Registering Filter for aliases {}: {} with async: {}", Arrays.asList(absUrlPatterns),
171 filter, asyncSupported);
172 paxWeb.registerFilter(filter, absUrlPatterns, new String[] { name }, new MapDictionary<>(params),
173 asyncSupported, osgiHttpContext);
174 registeredFilters.add(filter);
177 private String[] absolute(final List<String> relatives) {
178 return relatives.stream().map(urlPattern -> contextPath + urlPattern).toArray(String[]::new);
181 private void registerServlet(final HttpContext osgiHttpContext, final List<String> urlPatterns,
182 final String name, final Servlet servlet, final Map<String, String> params,
183 final Boolean asyncSupported) throws ServletException {
184 int loadOnStartup = 1;
185 String[] absUrlPatterns = absolute(urlPatterns);
186 LOG.info("Registering Servlet for aliases {}: {} with async: {}", absUrlPatterns,
187 servlet, asyncSupported);
188 paxWeb.registerServlet(servlet, name, absUrlPatterns, new MapDictionary<>(params), loadOnStartup,
189 asyncSupported, osgiHttpContext);
190 registeredServlets.add(servlet);
193 private void registerListener(final HttpContext osgiHttpContext, final ServletContextListener listener) {
194 paxWeb.registerEventListener(listener, osgiHttpContext);
195 registeredEventListeners.add(listener);
199 protected void removeRegistration() {
200 // The order is relevant here.. Servlets first, then Filters, Listeners last; this is the inverse of above
201 registeredServlets.forEach(paxWeb::unregisterServlet);
202 registeredFilters.forEach(paxWeb::unregisterFilter);
203 registeredEventListeners.forEach(paxWeb::unregisterEventListener);
204 registeredResources.forEach(paxWeb::unregister);