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;
10 import com.google.common.collect.ImmutableList;
11 import com.google.common.collect.ImmutableMap;
12 import java.util.List;
14 import javax.servlet.ServletContainerInitializer;
15 import javax.servlet.ServletContext;
16 import javax.servlet.ServletContextListener;
17 import javax.servlet.ServletRegistration;
18 import org.eclipse.jdt.annotation.NonNull;
21 * Web Context with URL prefix. AKA Web App or Servlet context.
24 * Its {@link WebContext.Builder} allows programmatic web component registration (as opposed to declarative e.g. via
25 * web.xml, OSGi HTTP Whiteboard blueprint integration, CXF BP etc.)
28 * This is preferable because:
30 * <li>using code instead of hiding class names in XML enables tools such as e.g. BND (in the maven-bundle-plugin) to
31 * correctly figure dependencies e.g. for OSGi Import-Package headers;</li>
32 * <li>explicit passing of web components instances, instead of providing class names in XML files and letting a web
33 * container create the new instances using the default constructor, solves a pesky dependency injection (DI)
34 * related problem which typically leads to weird hoops in code through {@code static} etc. that can be avoided
36 * <li>tests can more easily programmatically instantiate web components.</li>
40 * This, not surprisingly, looks somewhat like a Servlet (3.x+) {@link ServletContext}, which also allows programmatic
41 * dynamic registration e.g. via {@link ServletRegistration}; however in practice direct use of that API has been found
42 * to be problematic under OSGi, because it is intended for JSE and
43 * <a href="https://github.com/eclipse/jetty.project/issues/1395">does not easily appear to permit dynamic registration
44 * at any time</a> (only during Servlet container initialization time by {@link ServletContainerInitializer}), and is
45 * generally less clear to use than this simple API which intentionally maps directly to what one would have declared in
46 * a web.xml file. This API is also slightly more focused and drops a number of concepts that API has which we do not
47 * want to support here (including e.g. security, roles, multipart etc.)
50 * It also looks somewhat similar to the OSGi HttpService, but we want to avoid any org.osgi dependency (both API and
51 * impl) here, and that API is also less clear (and uses an ancient (!) {@link java.util.Dictionary} in its method
52 * signature), and -most importantly- simply does not support Filters and Listeners, only Servlets. The Pax Web API does
53 * extend the base OSGi API and adds supports for Filters, Listeners and context parameters, but is still OSGi specific,
54 * whereas this offers a much simpler standalone API without OSGi dependency. (The Pax Web API also has confusing
55 * signatures in its registerFilter() methods, where one can easily confuse which String[] is the urlPatterns; which we
56 * had initially done accidentally; and left AAA broken.)
59 * This is immutable, with a Builder, because contrary to a declarative approach in a file such as web.xml, the
60 * registration order very much matters (e.g. an context parameter added after a Servlet registration would not be seen
61 * by that Servlet; or a Filter added to protect a Servlet might not yet be active for an instant if the registerServlet
62 * is before the registerFilter). Therefore, this API enforces atomicity and lets clients first register everything on
63 * the Builder, and only then use {@link WebServer#registerWebContext(WebContext)}.
65 * @author Michael Vorburger.ch
67 public interface WebContext {
69 * Get path which will be used as URL prefix to all registered servlets and filters. Guaranteed to be non-empty
71 * @return {@link String} path
72 * @see "Java Servlet Specification Version 3.1, Section 3.5 Request Path Elements"
74 @NonNull String contextPath();
77 * Get flag value whether this context supports web sessions.
79 * @return boolean flag value
81 boolean supportsSessions();
84 * Get list of servlets.
86 * @return {@link List} list of {@link ServletDetails}
88 @NonNull List<ServletDetails> servlets();
91 * Get list of filters.
93 * @return {@link List} list of {@link FilterDetails}
95 @NonNull List<FilterDetails> filters();
98 * Get list of servlet context listeners.
100 * @return {@link List} list of {@link ServletContextListener}
102 @NonNull List<ServletContextListener> listeners();
105 * Get lis of resources (e.g. html files) that can be accessed via the URI namespace.
107 * @return {@link List} list of {@link ResourceDetails}
109 @NonNull List<ResourceDetails> resources();
112 * Get map of context params.
115 * These are the {@link ServletContext}s initial parameters; contrary to individual
116 * {@link ServletDetails#initParams()} and {@link FilterDetails#initParams()}. While a ServletContext accepts
117 * any Object as a parameter, that is not accepted in all implementations. Most notably OSGi HTTP Whiteboard
118 * specification allows only String values, hence we are enforcing that.
120 * @return {@link Map} context parameters map
122 @NonNull Map<String, String> contextParams();
125 * Create builder for {@code WebContext}.
127 * @return {@link Builder} builder instance
129 static @NonNull Builder builder() {
130 return new Builder();
134 * Builds instances of type {@link WebContext WebContext}. Initialize attributes and then invoke the
135 * {@link #build()} method to create an immutable instance.
137 * <p><em>{@code WebContext.Builder} is not thread-safe and generally should not be stored in a field or
138 * collection, but instead used immediately to create instances.</em>
140 final class Builder {
141 private record ImmutableWebContext(String contextPath, ImmutableList<ServletDetails> servlets,
142 ImmutableList<FilterDetails> filters, ImmutableList<ServletContextListener> listeners,
143 ImmutableList<ResourceDetails> resources, ImmutableMap<String, String> contextParams,
144 boolean supportsSessions) implements WebContext {
145 // Not much else here
148 private final ImmutableMap.Builder<String, String> contextParams = ImmutableMap.builder();
149 private final ImmutableList.Builder<ServletDetails> servlets = ImmutableList.builder();
150 private final ImmutableList.Builder<FilterDetails> filters = ImmutableList.builder();
151 private final ImmutableList.Builder<ServletContextListener> listeners = ImmutableList.builder();
152 private final ImmutableList.Builder<ResourceDetails> resources = ImmutableList.builder();
153 private String contextPath;
154 private boolean supportsSessions = true;
161 * Initializes the value for the {@link WebContext#contextPath() contextPath} attribute. As per Servlet
163 * @param contextPath The value for contextPath
164 * @return {@code this} builder for use in a chained invocation
165 * @throws IllegalArgumentException if {@code contextPath} does not meet specification criteria
166 * @throws NullPointerException if {code contextPath} is {@code null}
168 @SuppressWarnings("checkstyle:hiddenField")
169 public @NonNull Builder contextPath(final String contextPath) {
170 this.contextPath = ServletSpec.requireContextPath(contextPath);
175 * Adds one element to {@link WebContext#servlets() servlets} list.
177 * @param servlet A servlets element
178 * @return {@code this} builder for use in a chained invocation
179 * @throws NullPointerException if {code servlet} is {@code null}
181 public @NonNull Builder addServlet(final ServletDetails servlet) {
182 servlets.add(servlet);
187 * Adds one element to {@link WebContext#filters() filters} list.
189 * @param filter A filters element
190 * @return {@code this} builder for use in a chained invocation
191 * @throws NullPointerException if {code filter} is {@code null}
193 public @NonNull Builder addFilter(final FilterDetails filter) {
199 * Adds one element to {@link WebContext#listeners() listeners} list.
201 * @param listener A listeners element
202 * @return {@code this} builder for use in a chained invocation
203 * @throws NullPointerException if {code listener} is {@code null}
205 public @NonNull Builder addListener(final ServletContextListener listener) {
206 listeners.add(listener);
211 * Adds one element to {@link WebContext#resources() resources} list.
213 * @param resource A resources element
214 * @return {@code this} builder for use in a chained invocation
215 * @throws NullPointerException if {code resource} is {@code null}
217 public @NonNull Builder addResource(final ResourceDetails resource) {
218 resources.add(resource);
223 * Put one entry to the {@link WebContext#contextParams() contextParams} map.
225 * @param key The key in the contextParams map
226 * @param value The associated value in the contextParams map
227 * @return {@code this} builder for use in a chained invocation
228 * @throws NullPointerException if any argument is {@code null}
230 public @NonNull Builder putContextParam(final String key, final String value) {
231 contextParams.put(key, value);
236 * Initializes the value for the {@link WebContext#supportsSessions() supportsSessions} attribute.
238 * <p><em>If not set, this attribute will have a default value of {@code true}.</em>
240 * @param supportsSessions The value for supportsSessions
241 * @return {@code this} builder for use in a chained invocation
243 @SuppressWarnings("checkstyle:hiddenField")
244 public Builder supportsSessions(final boolean supportsSessions) {
245 this.supportsSessions = supportsSessions;
250 * Builds a new {@link WebContext WebContext}.
252 * @return An immutable instance of WebContext
253 * @throws IllegalStateException if any required attributes are missing
255 public @NonNull WebContext build() {
256 if (contextPath == null) {
257 throw new IllegalStateException("No contextPath specified");
259 return new ImmutableWebContext(contextPath, servlets.build(), filters.build(), listeners.build(),
260 resources.build(), contextParams.build(), supportsSessions);