<module>aaa-shiro</module>
<module>aaa-shiro-act</module>
<module>dependency-check</module>
+ <module>web</module>
</modules>
<build>
--- /dev/null
+<?xml version="1.0" encoding="UTF-8"?>
+<!--
+ Copyright © 2018 Red Hat, Inc. and others. All rights reserved.
+
+ This program and the accompanying materials are made available under the
+ terms of the Eclipse Public License v1.0 which accompanies this distribution,
+ and is available at http://www.eclipse.org/legal/epl-v10.html
+-->
+<project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
+ xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
+ <modelVersion>4.0.0</modelVersion>
+
+ <parent>
+ <groupId>org.opendaylight.odlparent</groupId>
+ <artifactId>bundle-parent</artifactId>
+ <version>3.0.2</version>
+ <relativePath />
+ </parent>
+
+ <groupId>org.opendaylight.aaa.web</groupId>
+ <artifactId>web-api</artifactId>
+ <version>0.8.0-SNAPSHOT</version>
+ <name>ODL :: infrautils :: ${project.artifactId}</name>
+ <packaging>bundle</packaging>
+
+ <dependencies>
+ <dependency>
+ <groupId>javax.servlet</groupId>
+ <artifactId>javax.servlet-api</artifactId>
+ </dependency>
+ <dependency>
+ <groupId>org.immutables</groupId>
+ <artifactId>value</artifactId>
+ </dependency>
+
+ <dependency>
+ <groupId>org.mockito</groupId>
+ <artifactId>mockito-core</artifactId>
+ </dependency>
+ <dependency>
+ <groupId>com.google.truth</groupId>
+ <artifactId>truth</artifactId>
+ </dependency>
+ <dependency>
+ <groupId>org.opendaylight.infrautils</groupId>
+ <artifactId>infrautils-testutils</artifactId>
+ <version>1.4.0-SNAPSHOT</version>
+ <scope>test</scope>
+ </dependency>
+ </dependencies>
+
+ <build>
+ <plugins>
+ <plugin>
+ <groupId>org.apache.aries.blueprint</groupId>
+ <artifactId>blueprint-maven-plugin</artifactId>
+ </plugin>
+ <plugin>
+ <groupId>org.apache.maven.plugins</groupId>
+ <artifactId>maven-checkstyle-plugin</artifactId>
+ <configuration>
+ <propertyExpansion>checkstyle.violationSeverity=error</propertyExpansion>
+ </configuration>
+ </plugin>
+ <plugin>
+ <groupId>org.codehaus.mojo</groupId>
+ <artifactId>findbugs-maven-plugin</artifactId>
+ <configuration>
+ <failOnError>true</failOnError>
+ </configuration>
+ </plugin>
+ </plugins>
+ </build>
+
+</project>
--- /dev/null
+/*
+ * Copyright (c) 2018 Red Hat, Inc. and others. All rights reserved.
+ *
+ * This program and the accompanying materials are made available under the
+ * terms of the Eclipse Public License v1.0 which accompanies this distribution,
+ * and is available at http://www.eclipse.org/legal/epl-v10.html
+ */
+package org.opendaylight.aaa.web;
+
+import java.util.List;
+import java.util.Map;
+import javax.servlet.Filter;
+import org.immutables.value.Value;
+import org.immutables.value.Value.Default;
+
+/**
+ * Details about a {@link Filter}.
+ *
+ * @author Michael Vorburger.ch
+ */
+@Value.Immutable
+@Value.Style(visibility = Value.Style.ImplementationVisibility.PRIVATE, depluralize = true)
+public interface FilterDetails {
+
+ static FilterDetailsBuilder builder() {
+ return new FilterDetailsBuilder();
+ }
+
+ Filter filter();
+
+ @Default default String name() {
+ return filter().getClass().getName();
+ }
+
+ List<String> urlPatterns();
+
+ Map<String, String> initParams();
+
+}
--- /dev/null
+/*
+ * Copyright (c) 2018 Red Hat, Inc. and others. All rights reserved.
+ *
+ * This program and the accompanying materials are made available under the
+ * terms of the Eclipse Public License v1.0 which accompanies this distribution,
+ * and is available at http://www.eclipse.org/legal/epl-v10.html
+ */
+package org.opendaylight.aaa.web;
+
+import java.util.List;
+import java.util.Map;
+import javax.servlet.Servlet;
+import org.immutables.value.Value;
+import org.immutables.value.Value.Default;
+
+/**
+ * Details about a {@link Servlet}.
+ *
+ * @author Michael Vorburger.ch
+ */
+@Value.Immutable
+@Value.Style(visibility = Value.Style.ImplementationVisibility.PRIVATE, depluralize = true)
+public interface ServletDetails {
+
+ static ServletDetailsBuilder builder() {
+ return new ServletDetailsBuilder();
+ }
+
+ Servlet servlet();
+
+ @Default default String name() {
+ return servlet().getClass().getName();
+ }
+
+ List<String> urlPatterns();
+
+ Map<String, String> initParams();
+
+}
--- /dev/null
+/*
+ * Copyright (c) 2018 Red Hat, Inc. and others. All rights reserved.
+ *
+ * This program and the accompanying materials are made available under the
+ * terms of the Eclipse Public License v1.0 which accompanies this distribution,
+ * and is available at http://www.eclipse.org/legal/epl-v10.html
+ */
+package org.opendaylight.aaa.web;
+
+import java.util.List;
+import java.util.Map;
+import javax.servlet.ServletContainerInitializer;
+import javax.servlet.ServletContext;
+import javax.servlet.ServletContextListener;
+import javax.servlet.ServletRegistration;
+import org.immutables.value.Value;
+import org.immutables.value.Value.Default;
+
+/**
+ * Web Context with URL prefix. AKA Web App or Servlet context.
+ *
+ * <p>
+ * Its {@link WebContextBuilder} allows programmatic web component registration
+ * (as opposed to declarative e.g. via web.xml, OSGi HTTP Whiteboard blueprint
+ * integration, CXF BP etc.)
+ *
+ * <p>
+ * This is preferable because:
+ * <ul>
+ * <li>using code instead of hiding class names in XML enables tools such as
+ * e.g. BND (in the maven-bundle-plugin) to correctly figure dependencies e.g.
+ * for OSGi Import-Package headers;
+ *
+ * <li>explicit passing of web components instances, instead of providing class
+ * names in XML files and letting a web container create the new instances using
+ * the default constructor, solves a pesky dependency injection (DI) related
+ * problem which typically leads to weird hoops in code through
+ * <code>static</code> etc. that can be avoided using this;
+ *
+ * <li>tests can more easily programmatically instantiate web components.
+ * </ul>
+ *
+ * <p>
+ * This, not surprisingly, looks somewhat like a Servlet (3.x)
+ * {@link ServletContext}, which also allows programmatic dynamic registration
+ * e.g. via {@link ServletRegistration}; however in practice direct use of that
+ * API has been found to be problematic under OSGi, because it is intended for
+ * JSE and <a href="https://github.com/eclipse/jetty.project/issues/1395">does
+ * not easily appear to permit dynamic registration at any time</a> (only during
+ * Servlet container initialization time by
+ * {@link ServletContainerInitializer}), and is generally less clear to use than
+ * this simple API which intentionally maps directly to what one would have
+ * declared in a web.xml file. This API is also slightly more focused and drops
+ * a number of concepts that API has which we do not want to support here
+ * (including e.g. security, roles, multipart etc.)
+ *
+ * <p>
+ * It also looks somewhat similar to the OSGi HttpService, but we want to avoid
+ * any org.osgi dependency (both API and impl) here, and that API is also less
+ * clear (and uses an ancient (!) {@link java.util.Dictionary} in its method
+ * signature), and -most importantly- simply does not support Filters and Listeners, only
+ * Servlets. The Pax Web API does extend the base OSGi API and adds supports for
+ * Filters, Listeners and context parameters, but is still OSGi specific,
+ * whereas this offers a much simpler standalone API without OSGi dependency.
+ * (The Pax Web API also has confusing signatures in its registerFilter() methods,
+ * where one can easily confuse which String[] is the urlPatterns;
+ * which we had initially done accidentally; and left AAA broken.)
+ *
+ * <p>
+ * This is immutable, with a Builder, because contrary to a declarative approach
+ * in a file such as web.xml, the registration order very much matters (e.g. an
+ * context parameter added after a Servlet registration would not be seen by that
+ * Servlet; or a Filter added to protect a Servlet might not yet be active
+ * for an instant if the registerServlet is before the registerFilter).
+ * Therefore, this API enforces atomicity and lets clients first register
+ * everything on the Builder, and only then use
+ * {@link WebServer#registerWebContext(WebContext)}.
+ *
+ * @author Michael Vorburger.ch
+ */
+@Value.Immutable
+@Value.Style(visibility = Value.Style.ImplementationVisibility.PRIVATE, depluralize = true)
+public abstract class WebContext {
+
+ public static WebContextBuilder builder() {
+ return new WebContextBuilder();
+ }
+
+ /**
+ * Path which will be used as URL prefix to all registered servlets and filters.
+ */
+ public abstract String contextPath();
+
+ /**
+ * Flag whether this context supports web sessions, defaults to true.
+ */
+ @Default
+ public boolean supportsSessions() {
+ return true;
+ }
+
+ /**
+ * Servlets.
+ */
+ public abstract List<ServletDetails> servlets();
+
+ /**
+ * Filters.
+ */
+ public abstract List<FilterDetails> filters();
+
+ /**
+ * Listeners.
+ */
+ public abstract List<ServletContextListener> listeners();
+
+ /**
+ * Context params. NB: These are the web context's wide parameters; contrary to
+ * individual {@link ServletDetails#initParams()} and
+ * {@link FilterDetails#initParams()}.
+ */
+ public abstract Map<String, String> contextParams();
+
+ @Value.Check
+ protected void check() {
+ servlets().forEach(servlet -> {
+ if (servlet.urlPatterns().isEmpty()) {
+ throw new IllegalArgumentException("Servlet has no URL: " + servlet.name());
+ }
+ });
+ filters().forEach(filter -> {
+ if (filter.urlPatterns().isEmpty()) {
+ throw new IllegalArgumentException("Filter has no URL: " + filter.name());
+ }
+ });
+ }
+}
--- /dev/null
+/*
+ * Copyright (c) 2018 Red Hat, Inc. and others. All rights reserved.
+ *
+ * This program and the accompanying materials are made available under the
+ * terms of the Eclipse Public License v1.0 which accompanies this distribution,
+ * and is available at http://www.eclipse.org/legal/epl-v10.html
+ */
+package org.opendaylight.aaa.web;
+
+/**
+ * {@link WebContext} registration.
+ * Allows to {@link #close()} the web context, which unregisters its servlets, filters and listeners.
+ *
+ * @author Michael Vorburger.ch
+ */
+public interface WebContextRegistration extends AutoCloseable {
+
+ @Override
+ void close(); // does not throw Exception
+
+}
--- /dev/null
+/*
+ * Copyright (c) 2018 Red Hat, Inc. and others. All rights reserved.
+ *
+ * This program and the accompanying materials are made available under the
+ * terms of the Eclipse Public License v1.0 which accompanies this distribution,
+ * and is available at http://www.eclipse.org/legal/epl-v10.html
+ */
+package org.opendaylight.aaa.web;
+
+import javax.servlet.ServletException;
+
+/**
+ * Web server (HTTP). This service API allows ODL applications to register web
+ * components programmatically, instead of using a web.xml declaratively; see
+ * the {@link WebContext} for why this is preferable.
+ *
+ * <p>
+ * This API has an OSGi-based as well as a "standalone" implementation suitable
+ * e.g. for tests.
+ *
+ * @author Michael Vorburger.ch
+ */
+public interface WebServer {
+
+ /**
+ * Register a new web context.
+ *
+ * @param webContext the web context
+ * @return registration which allows to close the context (and remove its servlets etc.)
+ * @throws ServletException if registration of any of the components of the web context failed
+ */
+ WebContextRegistration registerWebContext(WebContext webContext) throws ServletException;
+
+ /**
+ * Base URL of this web server, without any contexts. In production, this would
+ * likely be HTTPS with a well known hostname and fixed port configured e.g. in
+ * a Karaf etc/ configuration file. In tests, this would be typically be HTTP on
+ * localhost and an arbitrarily chosen port.
+ *
+ * @return base URL, with http[s] prefix and port, NOT ending in slash
+ */
+ String getBaseURL();
+
+}
--- /dev/null
+/*
+ * Copyright (c) 2018 Red Hat, Inc. and others. All rights reserved.
+ *
+ * This program and the accompanying materials are made available under the
+ * terms of the Eclipse Public License v1.0 which accompanies this distribution,
+ * and is available at http://www.eclipse.org/legal/epl-v10.html
+ */
+package org.opendaylight.aaa.web.tests;
+
+import static com.google.common.truth.Truth.assertThat;
+import static org.mockito.Mockito.mock;
+import static org.opendaylight.infrautils.testutils.Asserts.assertThrows;
+
+import javax.servlet.Filter;
+import javax.servlet.Servlet;
+import javax.servlet.ServletContextListener;
+import org.junit.Ignore;
+import org.junit.Test;
+import org.opendaylight.aaa.web.FilterDetails;
+import org.opendaylight.aaa.web.ServletDetails;
+import org.opendaylight.aaa.web.WebContext;
+import org.opendaylight.aaa.web.WebServer;
+
+/**
+ * Tests for Web Server {@link WebContext} API. These tests don't test an
+ * {@link WebServer} implementation; the purpose is just to compile time check
+ * against the API signatures, notably incl. their generated code.
+ *
+ * @author Michael Vorburger.ch
+ */
+public class WebContextApiTest {
+
+ @Test
+ public void testEmptyBuilder() {
+ assertThrows(IllegalStateException.class, () -> WebContext.builder().build());
+ }
+
+ @Test
+ public void testMinimalBuilder() {
+ assertThat(WebContext.builder().contextPath("test").build().supportsSessions()).isTrue();
+ assertThat(WebContext.builder().contextPath("test").supportsSessions(false).build().contextPath())
+ .isEqualTo("test");
+ }
+
+ @Test
+ public void testAddSimpleServlet() {
+ WebContext webContext = WebContext.builder().contextPath("test")
+ .addServlet(ServletDetails.builder().servlet(mock(Servlet.class)).addUrlPattern("test").build())
+ .build();
+ assertThat(webContext.servlets()).hasSize(1);
+ ServletDetails firstServletDetail = webContext.servlets().get(0);
+ assertThat(firstServletDetail.name())
+ .startsWith("$javax.servlet.Servlet$$EnhancerByMockitoWithCGLIB$$");
+ assertThat(firstServletDetail.initParams()).isEmpty();
+ }
+
+ @Test
+ public void testAddFullServlet() {
+ WebContext.builder().contextPath("test").addServlet(ServletDetails.builder().servlet(mock(Servlet.class))
+ .addUrlPattern("test").addUrlPattern("another").name("custom").putInitParam("key", "value").build())
+ .build();
+ }
+
+ @Test
+ public void testAddFilter() {
+ WebContext.builder().contextPath("test")
+ .addFilter(FilterDetails.builder().filter(mock(Filter.class)).addUrlPattern("test").build()).build();
+ }
+
+ @Test
+ public void testAddListener() {
+ assertThat(WebContext.builder().contextPath("test").addListener(mock(ServletContextListener.class)).build()
+ .listeners()).isNotEmpty();
+ }
+
+ @Test
+ public void testContextParam() {
+ assertThat(WebContext.builder().contextPath("test").putContextParam("key", "value").build().contextParams())
+ .containsExactly("key", "value").inOrder();
+ }
+
+ @Test
+ @Ignore // TODO
+ public void testBadContextPath() {
+ assertThrows(IllegalArgumentException.class, () -> WebContext.builder().contextPath("test/sub").build());
+ assertThrows(IllegalArgumentException.class, () -> WebContext.builder().contextPath("test space").build());
+ assertThrows(IllegalArgumentException.class, () -> WebContext.builder().contextPath("/test").build());
+ assertThrows(IllegalArgumentException.class, () -> WebContext.builder().contextPath("test/").build());
+ }
+
+ @Test
+ @Ignore // TODO
+ public void testBadServletWithoutAnyURL() {
+ assertThrows(IllegalArgumentException.class, () -> WebContext.builder().contextPath("test")
+ .addServlet(ServletDetails.builder().servlet(mock(Servlet.class)).build()).build());
+ }
+
+ @Test
+ @Ignore // TODO
+ public void testBadFilterWithoutAnyURL() {
+ assertThrows(IllegalArgumentException.class, () -> WebContext.builder().contextPath("test")
+ .addFilter(FilterDetails.builder().filter(mock(Filter.class)).build()).build());
+ }
+
+}
--- /dev/null
+<?xml version="1.0" encoding="UTF-8"?>
+<!-- vi: set et smarttab sw=2 tabstop=2: -->
+<!--
+ Copyright © 2018 Red Hat, Inc. and others. All rights reserved.
+
+ This program and the accompanying materials are made available under the
+ terms of the Eclipse Public License v1.0 which accompanies this distribution,
+ and is available at http://www.eclipse.org/legal/epl-v10.html
+-->
+<project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
+ xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
+ <modelVersion>4.0.0</modelVersion>
+
+ <parent>
+ <groupId>org.opendaylight.odlparent</groupId>
+ <artifactId>odlparent-lite</artifactId>
+ <version>3.0.2</version>
+ <relativePath />
+ </parent>
+
+ <groupId>org.opendaylight.aaa.web</groupId>
+ <artifactId>web-aggregator</artifactId>
+ <version>0.8.0-SNAPSHOT</version>
+ <packaging>pom</packaging>
+ <name>ODL :: infrautils :: ${project.artifactId}</name>
+
+ <modules>
+ <module>api</module>
+ </modules>
+</project>