summaryrefslogtreecommitdiffstats
path: root/jdisc_http_service
diff options
context:
space:
mode:
authorBjørn Christian Seime <bjorncs@verizonmedia.com>2020-11-11 15:53:51 +0100
committerBjørn Christian Seime <bjorncs@verizonmedia.com>2020-11-11 15:55:32 +0100
commit5c3a55faac3ae620a51f6b0388f8e1d091e725f0 (patch)
tree8fec1bb46dafc7f218e8ffd314a0b9f46f733198 /jdisc_http_service
parent1b7e6a0dfb0b8686aeddce7418d28bbdfd4909c1 (diff)
Remove implicit and potensially false dependency on jdisc_http_service
Required for later changes to jdisc_http_service. Removes jdisc_http_service from provided-dependencies.
Diffstat (limited to 'jdisc_http_service')
-rw-r--r--jdisc_http_service/src/main/java/com/yahoo/jdisc/http/server/jetty/FilterBindings.java (renamed from jdisc_http_service/src/main/java/com/yahoo/jdisc/http/server/FilterBindings.java)0
-rw-r--r--jdisc_http_service/src/main/java/com/yahoo/jdisc/http/server/jetty/FilterBindingsProvider.java62
-rw-r--r--jdisc_http_service/src/main/java/com/yahoo/jdisc/http/server/jetty/FilterChainRepository.java201
-rw-r--r--jdisc_http_service/src/main/java/com/yahoo/jdisc/http/server/jetty/FilterUtil.java105
-rw-r--r--jdisc_http_service/src/main/java/com/yahoo/jdisc/http/server/package-info.java4
-rw-r--r--jdisc_http_service/src/test/java/com/yahoo/container/jdisc/FilterBindingsProviderTest.java203
6 files changed, 571 insertions, 4 deletions
diff --git a/jdisc_http_service/src/main/java/com/yahoo/jdisc/http/server/FilterBindings.java b/jdisc_http_service/src/main/java/com/yahoo/jdisc/http/server/jetty/FilterBindings.java
index 4dabed41bc6..4dabed41bc6 100644
--- a/jdisc_http_service/src/main/java/com/yahoo/jdisc/http/server/FilterBindings.java
+++ b/jdisc_http_service/src/main/java/com/yahoo/jdisc/http/server/jetty/FilterBindings.java
diff --git a/jdisc_http_service/src/main/java/com/yahoo/jdisc/http/server/jetty/FilterBindingsProvider.java b/jdisc_http_service/src/main/java/com/yahoo/jdisc/http/server/jetty/FilterBindingsProvider.java
new file mode 100644
index 00000000000..4f94b18f5bc
--- /dev/null
+++ b/jdisc_http_service/src/main/java/com/yahoo/jdisc/http/server/jetty/FilterBindingsProvider.java
@@ -0,0 +1,62 @@
+// Copyright 2017 Yahoo Holdings. Licensed under the terms of the Apache 2.0 license. See LICENSE in the project root.
+package com.yahoo.container.jdisc;
+
+import com.yahoo.component.ComponentId;
+import com.yahoo.component.provider.ComponentRegistry;
+import com.yahoo.container.di.componentgraph.Provider;
+import com.yahoo.jdisc.http.server.jetty.FilterChainRepository;
+import com.yahoo.jdisc.application.BindingRepository;
+import com.yahoo.jdisc.http.ServerConfig;
+import com.yahoo.jdisc.http.filter.RequestFilter;
+import com.yahoo.jdisc.http.filter.ResponseFilter;
+import com.yahoo.jdisc.http.filter.SecurityRequestFilter;
+import com.yahoo.jdisc.http.server.jetty.FilterBindings;
+
+import java.util.ArrayList;
+import java.util.List;
+
+/**
+ * Provides filter bindings based on vespa config.
+ *
+ * @author bakksjo
+ */
+public class FilterBindingsProvider implements Provider<FilterBindings> {
+
+ final BindingRepository<RequestFilter> requestFilters = new BindingRepository<>();
+ final BindingRepository<ResponseFilter> responseFilters = new BindingRepository<>();
+
+ public FilterBindingsProvider(ComponentId componentId,
+ ServerConfig config,
+ FilterChainRepository filterChainRepository,
+ ComponentRegistry<SecurityRequestFilter> legacyRequestFilters) {
+ ComponentId serverId = componentId.getNamespace();
+ try {
+ FilterUtil.setupFilters(
+ componentId,
+ legacyRequestFilters,
+ toFilterSpecs(config.filter()),
+ filterChainRepository,
+ requestFilters,
+ responseFilters);
+ } catch (Exception e) {
+ throw new RuntimeException("Invalid config for http server " + serverId, e);
+ }
+ }
+
+ private List<FilterUtil.FilterSpec> toFilterSpecs(List<ServerConfig.Filter> inFilters) {
+ List<FilterUtil.FilterSpec> outFilters = new ArrayList<>();
+ for (ServerConfig.Filter inFilter : inFilters) {
+ outFilters.add(new FilterUtil.FilterSpec(inFilter.id(), inFilter.binding()));
+ }
+ return outFilters;
+ }
+
+ @Override
+ public FilterBindings get() {
+ return new FilterBindings(requestFilters, responseFilters);
+ }
+
+ @Override
+ public void deconstruct() {}
+
+}
diff --git a/jdisc_http_service/src/main/java/com/yahoo/jdisc/http/server/jetty/FilterChainRepository.java b/jdisc_http_service/src/main/java/com/yahoo/jdisc/http/server/jetty/FilterChainRepository.java
new file mode 100644
index 00000000000..31bceca9337
--- /dev/null
+++ b/jdisc_http_service/src/main/java/com/yahoo/jdisc/http/server/jetty/FilterChainRepository.java
@@ -0,0 +1,201 @@
+// Copyright 2017 Yahoo Holdings. Licensed under the terms of the Apache 2.0 license. See LICENSE in the project root.
+package com.yahoo.container.http.filter;
+
+import com.yahoo.component.AbstractComponent;
+import com.yahoo.component.ComponentId;
+import com.yahoo.component.ComponentSpecification;
+import com.yahoo.component.chain.Chain;
+import com.yahoo.component.chain.ChainedComponent;
+import com.yahoo.component.chain.ChainsConfigurer;
+import com.yahoo.component.chain.model.ChainsModel;
+import com.yahoo.component.chain.model.ChainsModelBuilder;
+import com.yahoo.component.provider.ComponentRegistry;
+import com.yahoo.container.core.ChainsConfig;
+import com.yahoo.jdisc.http.filter.RequestFilter;
+import com.yahoo.jdisc.http.filter.ResponseFilter;
+import com.yahoo.jdisc.http.filter.SecurityRequestFilter;
+import com.yahoo.jdisc.http.filter.SecurityRequestFilterChain;
+import com.yahoo.jdisc.http.filter.SecurityResponseFilter;
+import com.yahoo.jdisc.http.filter.SecurityResponseFilterChain;
+import com.yahoo.jdisc.http.filter.chain.RequestFilterChain;
+import com.yahoo.jdisc.http.filter.chain.ResponseFilterChain;
+import com.yahoo.processing.execution.chain.ChainRegistry;
+
+import java.util.ArrayList;
+import java.util.Collections;
+import java.util.List;
+import java.util.Set;
+import java.util.logging.Logger;
+
+import static java.util.Collections.emptyList;
+import static java.util.stream.Collectors.toList;
+import static java.util.stream.Collectors.toSet;
+
+/**
+ * Creates JDisc request/response filter chains.
+ *
+ * @author Tony Vaagenes
+ * @author bjorncs
+ */
+public class FilterChainRepository extends AbstractComponent {
+ private static final Logger log = Logger.getLogger(FilterChainRepository.class.getName());
+
+ private final ComponentRegistry<Object> filterAndChains;
+
+ public FilterChainRepository(ChainsConfig chainsConfig,
+ ComponentRegistry<RequestFilter> requestFilters,
+ ComponentRegistry<ResponseFilter> responseFilters,
+ ComponentRegistry<SecurityRequestFilter> securityRequestFilters,
+ ComponentRegistry<SecurityResponseFilter> securityResponseFilters) {
+ ComponentRegistry<Object> filterAndChains = new ComponentRegistry<>();
+ addAllFilters(filterAndChains, requestFilters, responseFilters, securityRequestFilters, securityResponseFilters);
+ addAllChains(filterAndChains, chainsConfig, requestFilters, responseFilters, securityRequestFilters, securityResponseFilters);
+ filterAndChains.freeze();
+ this.filterAndChains = filterAndChains;
+ }
+
+ public Object getFilter(ComponentSpecification componentSpecification) {
+ return filterAndChains.getComponent(componentSpecification);
+ }
+
+ private static void addAllFilters(ComponentRegistry<Object> destination,
+ ComponentRegistry<?>... registries) {
+ for (ComponentRegistry<?> registry : registries) {
+ registry.allComponentsById()
+ .forEach((id, filter) -> destination.register(id, wrapIfSecurityFilter(filter)));
+ }
+ }
+
+ private static void addAllChains(ComponentRegistry<Object> destination,
+ ChainsConfig chainsConfig,
+ ComponentRegistry<?>... filters) {
+ ChainRegistry<FilterWrapper> chainRegistry = buildChainRegistry(chainsConfig, filters);
+ chainRegistry.allComponents()
+ .forEach(chain -> destination.register(chain.getId(), toJDiscChain(chain)));
+ }
+
+ private static ChainRegistry<FilterWrapper> buildChainRegistry(ChainsConfig chainsConfig,
+ ComponentRegistry<?>... filters) {
+ ChainRegistry<FilterWrapper> chainRegistry = new ChainRegistry<>();
+ ChainsModel chainsModel = ChainsModelBuilder.buildFromConfig(chainsConfig);
+ ChainsConfigurer.prepareChainRegistry(chainRegistry, chainsModel, allFiltersWrapped(filters));
+ removeEmptyChains(chainRegistry);
+ chainRegistry.freeze();
+ return chainRegistry;
+ }
+
+ private static void removeEmptyChains(ChainRegistry<FilterWrapper> chainRegistry) {
+ chainRegistry.allComponents().stream()
+ .filter(chain -> chain.components().isEmpty())
+ .map(Chain::getId)
+ .peek(id -> log.warning("Removing empty filter chain: " + id))
+ .forEach(chainRegistry::unregister);
+ }
+
+ @SuppressWarnings("unchecked")
+ private static Object toJDiscChain(Chain<FilterWrapper> chain) {
+ if (chain.components().isEmpty())
+ throw new IllegalArgumentException("Empty filter chain: " + chain.getId());
+ checkFilterTypesCompatible(chain);
+ List<?> jdiscFilters = chain.components().stream()
+ .map(filterWrapper -> filterWrapper.filter)
+ .collect(toList());
+ List<?> wrappedFilters = wrapSecurityFilters(jdiscFilters);
+ Object head = wrappedFilters.get(0);
+ if (wrappedFilters.size() == 1) return head;
+ else if (head instanceof RequestFilter)
+ return RequestFilterChain.newInstance((List<RequestFilter>) wrappedFilters);
+ else if (head instanceof ResponseFilter)
+ return ResponseFilterChain.newInstance((List<ResponseFilter>) wrappedFilters);
+ throw new IllegalStateException();
+ }
+
+ private static List<?> wrapSecurityFilters(List<?> filters) {
+ List<Object> aggregatedSecurityFilters = new ArrayList<>();
+ List<Object> wrappedFilters = new ArrayList<>();
+ for (Object filter : filters) {
+ if (isSecurityFilter(filter)) {
+ aggregatedSecurityFilters.add(filter);
+ } else {
+ if (!aggregatedSecurityFilters.isEmpty()) {
+ wrappedFilters.add(createSecurityChain(aggregatedSecurityFilters));
+ aggregatedSecurityFilters.clear();
+ }
+ wrappedFilters.add(filter);
+ }
+ }
+ if (!aggregatedSecurityFilters.isEmpty()) {
+ wrappedFilters.add(createSecurityChain(aggregatedSecurityFilters));
+ }
+ return wrappedFilters;
+ }
+
+ private static void checkFilterTypesCompatible(Chain<FilterWrapper> chain) {
+ Set<ComponentId> requestFilters = chain.components().stream()
+ .filter(filter -> filter instanceof RequestFilter || filter instanceof SecurityRequestFilter)
+ .map(FilterWrapper::getId)
+ .collect(toSet());
+ Set<ComponentId> responseFilters = chain.components().stream()
+ .filter(filter -> filter instanceof ResponseFilter || filter instanceof SecurityResponseFilter)
+ .map(FilterWrapper::getId)
+ .collect(toSet());
+ if (!requestFilters.isEmpty() && !responseFilters.isEmpty()) {
+ throw new IllegalArgumentException(
+ String.format(
+ "Can't mix request and response filters in chain %s: request filters: %s, response filters: %s.",
+ chain.getId(), requestFilters, responseFilters));
+ }
+ }
+
+ private static ComponentRegistry<FilterWrapper> allFiltersWrapped(ComponentRegistry<?>... registries) {
+ ComponentRegistry<FilterWrapper> wrappedFilters = new ComponentRegistry<>();
+ for (ComponentRegistry<?> registry : registries) {
+ registry.allComponentsById()
+ .forEach((id, filter) -> wrappedFilters.register(id, new FilterWrapper(id, filter)));
+ }
+ wrappedFilters.freeze();
+ return wrappedFilters;
+ }
+
+ private static Object wrapIfSecurityFilter(Object filter) {
+ if (isSecurityFilter(filter)) return createSecurityChain(Collections.singletonList(filter));
+ return filter;
+ }
+
+ @SuppressWarnings("unchecked")
+ private static Object createSecurityChain(List<?> filters) {
+ Object head = filters.get(0);
+ if (head instanceof SecurityRequestFilter)
+ return SecurityRequestFilterChain.newInstance((List<SecurityRequestFilter>) filters);
+ else if (head instanceof SecurityResponseFilter)
+ return SecurityResponseFilterChain.newInstance((List<SecurityResponseFilter>) filters);
+ throw new IllegalArgumentException("Unexpected class " + head.getClass());
+ }
+
+ private static boolean isSecurityFilter(Object filter) {
+ return filter instanceof SecurityRequestFilter || filter instanceof SecurityResponseFilter;
+ }
+
+ private static class FilterWrapper extends ChainedComponent {
+ public final Object filter;
+ public final Class<?> filterType;
+
+ public FilterWrapper(ComponentId id, Object filter) {
+ super(id);
+ this.filter = filter;
+ this.filterType = getFilterType(filter);
+ }
+
+ private static Class<?> getFilterType(Object filter) {
+ if (filter instanceof RequestFilter)
+ return RequestFilter.class;
+ else if (filter instanceof ResponseFilter)
+ return ResponseFilter.class;
+ else if (filter instanceof SecurityRequestFilter)
+ return SecurityRequestFilter.class;
+ else if (filter instanceof SecurityResponseFilter)
+ return SecurityResponseFilter.class;
+ throw new IllegalArgumentException("Unsupported filter type: " + filter.getClass().getName());
+ }
+ }
+}
diff --git a/jdisc_http_service/src/main/java/com/yahoo/jdisc/http/server/jetty/FilterUtil.java b/jdisc_http_service/src/main/java/com/yahoo/jdisc/http/server/jetty/FilterUtil.java
new file mode 100644
index 00000000000..39139ed5927
--- /dev/null
+++ b/jdisc_http_service/src/main/java/com/yahoo/jdisc/http/server/jetty/FilterUtil.java
@@ -0,0 +1,105 @@
+// Copyright 2017 Yahoo Holdings. Licensed under the terms of the Apache 2.0 license. See LICENSE in the project root.
+package com.yahoo.container.jdisc;
+
+import com.yahoo.component.ComponentId;
+import com.yahoo.component.ComponentSpecification;
+import com.yahoo.component.provider.ComponentRegistry;
+import com.yahoo.jdisc.http.server.jetty.FilterChainRepository;
+import com.yahoo.jdisc.application.BindingRepository;
+import com.yahoo.jdisc.application.UriPattern;
+import com.yahoo.jdisc.http.filter.RequestFilter;
+import com.yahoo.jdisc.http.filter.ResponseFilter;
+import com.yahoo.jdisc.http.filter.SecurityRequestFilter;
+import com.yahoo.jdisc.http.filter.SecurityRequestFilterChain;
+
+import java.util.List;
+
+/**
+ * Helper class to set up filter binding repositories based on config.
+ *
+ * @author Øyvind Bakksjø
+ */
+class FilterUtil {
+
+ private static final ComponentId SEARCH_SERVER_COMPONENT_ID = ComponentId.fromString("SearchServer");
+
+ private final BindingRepository<RequestFilter> requestFilters;
+ private final BindingRepository<ResponseFilter> responseFilters;
+
+ private FilterUtil(BindingRepository<RequestFilter> requestFilters, BindingRepository<ResponseFilter> responseFilters) {
+ this.requestFilters = requestFilters;
+ this.responseFilters = responseFilters;
+ }
+
+ private void configureFilters(List<FilterSpec> filtersConfig, FilterChainRepository filterChainRepository) {
+ for (FilterSpec filterConfig : filtersConfig) {
+ Object filter = filterChainRepository.getFilter(ComponentSpecification.fromString(filterConfig.getId()));
+ if (filter == null) {
+ throw new RuntimeException("No http filter with id " + filterConfig.getId());
+ }
+ addFilter(filter, filterConfig.getBinding());
+ }
+ }
+
+ private void addFilter(Object filter, String binding) {
+ if (filter instanceof RequestFilter && filter instanceof ResponseFilter) {
+ throw new RuntimeException("The filter " + filter.getClass().getName() +
+ " is unsupported since it's both a RequestFilter and a ResponseFilter.");
+ } else if (filter instanceof RequestFilter) {
+ requestFilters.put(new UriPattern(binding), (RequestFilter) filter);
+ } else if (filter instanceof ResponseFilter) {
+ responseFilters.put(new UriPattern(binding), (ResponseFilter) filter);
+ } else {
+ throw new RuntimeException("Unknown filter type " + filter.getClass().getName());
+ }
+ }
+
+ //TVT: remove
+ private void configureLegacyFilters(ComponentId id, ComponentRegistry<SecurityRequestFilter> legacyRequestFilters) {
+ ComponentId serverName = id.getNamespace();
+ if (SEARCH_SERVER_COMPONENT_ID.equals(serverName) && !legacyRequestFilters.allComponents().isEmpty()) {
+ requestFilters.bind("http://*/*",
+ SecurityRequestFilterChain.newInstance(legacyRequestFilters.allComponents()));
+ }
+ }
+
+ /**
+ * Populates binding repositories with filters based on config.
+ *
+ * @param requestFilters output argument that will be mutated
+ * @param responseFilters output argument that will be mutated
+ */
+ public static void setupFilters(ComponentId componentId,
+ ComponentRegistry<SecurityRequestFilter> legacyRequestFilters,
+ List<FilterSpec> filtersConfig,
+ FilterChainRepository filterChainRepository,
+ BindingRepository<RequestFilter> requestFilters,
+ BindingRepository<ResponseFilter> responseFilters) {
+ FilterUtil filterUtil = new FilterUtil(requestFilters, responseFilters);
+
+ // TODO: remove
+ filterUtil.configureLegacyFilters(componentId, legacyRequestFilters);
+
+ filterUtil.configureFilters(filtersConfig, filterChainRepository);
+ }
+
+ public static class FilterSpec {
+
+ private final String id;
+ private final String binding;
+
+ public FilterSpec(String id, String binding) {
+ this.id = id;
+ this.binding = binding;
+ }
+
+ public String getId() {
+ return id;
+ }
+
+ public String getBinding() {
+ return binding;
+ }
+ }
+
+}
diff --git a/jdisc_http_service/src/main/java/com/yahoo/jdisc/http/server/package-info.java b/jdisc_http_service/src/main/java/com/yahoo/jdisc/http/server/package-info.java
deleted file mode 100644
index f6afffbaff0..00000000000
--- a/jdisc_http_service/src/main/java/com/yahoo/jdisc/http/server/package-info.java
+++ /dev/null
@@ -1,4 +0,0 @@
-// Copyright 2017 Yahoo Holdings. Licensed under the terms of the Apache 2.0 license. See LICENSE in the project root.
-@ExportPackage
-package com.yahoo.jdisc.http.server;
-import com.yahoo.osgi.annotation.ExportPackage;
diff --git a/jdisc_http_service/src/test/java/com/yahoo/container/jdisc/FilterBindingsProviderTest.java b/jdisc_http_service/src/test/java/com/yahoo/container/jdisc/FilterBindingsProviderTest.java
new file mode 100644
index 00000000000..6d711da749e
--- /dev/null
+++ b/jdisc_http_service/src/test/java/com/yahoo/container/jdisc/FilterBindingsProviderTest.java
@@ -0,0 +1,203 @@
+// Copyright 2017 Yahoo Holdings. Licensed under the terms of the Apache 2.0 license. See LICENSE in the project root.
+package com.yahoo.container.jdisc;
+
+import com.yahoo.component.ComponentId;
+import com.yahoo.component.provider.ComponentRegistry;
+import com.yahoo.container.core.ChainsConfig;
+import com.yahoo.jdisc.http.server.jetty.FilterBindingsProvider;
+import com.yahoo.jdisc.http.server.jetty.FilterChainRepository;
+import com.yahoo.jdisc.application.BindingRepository;
+import com.yahoo.jdisc.application.UriPattern;
+import com.yahoo.jdisc.http.ServerConfig;
+import com.yahoo.jdisc.http.filter.RequestFilter;
+import com.yahoo.jdisc.http.filter.ResponseFilter;
+import com.yahoo.jdisc.http.server.jetty.FilterBindings;
+import org.hamcrest.Description;
+import org.hamcrest.Matcher;
+import org.hamcrest.TypeSafeMatcher;
+import org.junit.Test;
+
+import java.util.Arrays;
+import java.util.HashSet;
+import java.util.Map;
+import java.util.Set;
+
+import static org.hamcrest.CoreMatchers.not;
+import static org.junit.Assert.assertFalse;
+import static org.junit.Assert.assertThat;
+import static org.junit.Assert.assertNotNull;
+import static org.junit.Assert.assertTrue;
+import static org.junit.Assert.fail;
+import static org.mockito.Mockito.mock;
+
+/**
+ * @author bakksjo
+ */
+public class FilterBindingsProviderTest {
+ final ServerConfig.Builder configBuilder = new ServerConfig.Builder();
+
+ @Test
+ public void requireThatEmptyInputGivesEmptyOutput() {
+ final FilterChainRepository filterChainRepository = new FilterChainRepository(
+ new ChainsConfig(new ChainsConfig.Builder()),
+ new ComponentRegistry<>(),
+ new ComponentRegistry<>(),
+ new ComponentRegistry<>(),
+ new ComponentRegistry<>());
+
+ final FilterBindingsProvider provider = new FilterBindingsProvider(
+ new ComponentId("foo"),
+ new ServerConfig(configBuilder),
+ filterChainRepository,
+ new ComponentRegistry<>());
+
+ final FilterBindings filterBindings = provider.get();
+
+ assertNotNull(filterBindings);
+ assertFalse(filterBindings.getRequestFilters().iterator().hasNext());
+ assertFalse(filterBindings.getResponseFilters().iterator().hasNext());
+ }
+
+ @Test
+ public void requireThatCorrectlyConfiguredFiltersAreIncluded() {
+ final String requestFilter1Id = "requestFilter1";
+ final String requestFilter2Id = "requestFilter2";
+ final String requestFilter3Id = "requestFilter3";
+ final String responseFilter1Id = "responseFilter1";
+ final String responseFilter2Id = "responseFilter2";
+ final String responseFilter3Id = "responseFilter3";
+
+ // Set up config.
+ configBuilder.filter(new ServerConfig.Filter.Builder().id(requestFilter1Id).binding("http://*/a"));
+ configBuilder.filter(new ServerConfig.Filter.Builder().id(requestFilter2Id).binding("http://*/b"));
+ configBuilder.filter(new ServerConfig.Filter.Builder().id(responseFilter1Id).binding("http://*/c"));
+ configBuilder.filter(new ServerConfig.Filter.Builder().id(responseFilter3Id).binding("http://*/d"));
+
+ // Set up registry.
+ final ComponentRegistry<RequestFilter> availableRequestFilters = new ComponentRegistry<>();
+ final RequestFilter requestFilter1Instance = mock(RequestFilter.class);
+ final RequestFilter requestFilter2Instance = mock(RequestFilter.class);
+ final RequestFilter requestFilter3Instance = mock(RequestFilter.class);
+ availableRequestFilters.register(ComponentId.fromString(requestFilter1Id), requestFilter1Instance);
+ availableRequestFilters.register(ComponentId.fromString(requestFilter2Id), requestFilter2Instance);
+ availableRequestFilters.register(ComponentId.fromString(requestFilter3Id), requestFilter3Instance);
+ final ComponentRegistry<ResponseFilter> availableResponseFilters = new ComponentRegistry<>();
+ final ResponseFilter responseFilter1Instance = mock(ResponseFilter.class);
+ final ResponseFilter responseFilter2Instance = mock(ResponseFilter.class);
+ final ResponseFilter responseFilter3Instance = mock(ResponseFilter.class);
+ availableResponseFilters.register(ComponentId.fromString(responseFilter1Id), responseFilter1Instance);
+ availableResponseFilters.register(ComponentId.fromString(responseFilter2Id), responseFilter2Instance);
+ availableResponseFilters.register(ComponentId.fromString(responseFilter3Id), responseFilter3Instance);
+ final FilterChainRepository filterChainRepository = new FilterChainRepository(
+ new ChainsConfig(new ChainsConfig.Builder()),
+ availableRequestFilters,
+ availableResponseFilters,
+ new ComponentRegistry<>(),
+ new ComponentRegistry<>());
+
+ // Set up the provider that we aim to test.
+ final FilterBindingsProvider provider = new FilterBindingsProvider(
+ new ComponentId("foo"),
+ new ServerConfig(configBuilder),
+ filterChainRepository,
+ new ComponentRegistry<>());
+
+ // Execute.
+ final FilterBindings filterBindings = provider.get();
+
+ // Verify.
+ assertNotNull(filterBindings);
+ assertThat(
+ filterBindings.getRequestFilters(),
+ containsFilters(requestFilter1Instance, requestFilter2Instance));
+ assertThat(
+ filterBindings.getRequestFilters(),
+ not(containsFilters(requestFilter3Instance)));
+ assertThat(
+ filterBindings.getResponseFilters(),
+ containsFilters(responseFilter1Instance, responseFilter3Instance));
+ assertThat(
+ filterBindings.getResponseFilters(),
+ not(containsFilters(responseFilter2Instance)));
+ }
+
+ private interface DualRoleFilter extends RequestFilter, ResponseFilter {}
+
+ @Test
+ public void requireThatInstanceCanNotBeBothRequestAndResponseFilter() {
+ final String filterId = "filter";
+
+ // Set up config.
+ configBuilder.filter(new ServerConfig.Filter.Builder().id(filterId).binding("http://*/*"));
+
+ // Set up registry.
+ final DualRoleFilter filterInstance = mock(DualRoleFilter.class);
+ final ComponentRegistry<RequestFilter> availableRequestFilters = new ComponentRegistry<>();
+ availableRequestFilters.register(ComponentId.fromString(filterId), filterInstance);
+ final FilterChainRepository filterChainRepository = new FilterChainRepository(
+ new ChainsConfig(new ChainsConfig.Builder()),
+ availableRequestFilters,
+ new ComponentRegistry<>(),
+ new ComponentRegistry<>(),
+ new ComponentRegistry<>());
+
+ try {
+ new FilterBindingsProvider(
+ new ComponentId("foo"),
+ new ServerConfig(configBuilder),
+ filterChainRepository,
+ new ComponentRegistry<>());
+ fail("Dual-role filter should not be accepted");
+ } catch (RuntimeException e) {
+ assertTrue(e.getMessage().contains("Invalid config"));
+ }
+ }
+
+ @Test
+ public void requireThatConfigWithUnknownReferenceFails() {
+ // Set up config.
+ configBuilder.filter(new ServerConfig.Filter.Builder().id("someFilter").binding("http://*/*"));
+
+ // Set up registry.
+ final FilterChainRepository filterChainRepository = new FilterChainRepository(
+ new ChainsConfig(new ChainsConfig.Builder()),
+ new ComponentRegistry<>(),
+ new ComponentRegistry<>(),
+ new ComponentRegistry<>(),
+ new ComponentRegistry<>());
+
+ try {
+ new FilterBindingsProvider(
+ new ComponentId("foo"),
+ new ServerConfig(configBuilder),
+ filterChainRepository,
+ new ComponentRegistry<>());
+ fail("Config with unknown filter reference should not be accepted");
+ } catch (RuntimeException e) {
+ assertTrue(e.getMessage().contains("Invalid config"));
+ }
+ }
+
+ @SafeVarargs
+ @SuppressWarnings("varargs")
+ private static <T> Matcher<? super BindingRepository<T>> containsFilters(
+ final T... requiredInstances) {
+ return new TypeSafeMatcher<>() {
+ private final Set<T> requiredFilterSet = new HashSet<>(Arrays.asList(requiredInstances));
+
+ @Override
+ protected boolean matchesSafely(final BindingRepository<T> actualInstances) {
+ final Set<T> notFoundFilterSet = new HashSet<>(requiredFilterSet);
+ for (final Map.Entry<UriPattern, T> actualEntry : actualInstances) {
+ notFoundFilterSet.remove(actualEntry.getValue());
+ }
+ return notFoundFilterSet.isEmpty();
+ }
+
+ @Override
+ public void describeTo(final Description description) {
+ description.appendText("BindingRepository containing " + requiredFilterSet);
+ }
+ };
+ }
+}