<commit_before>/*
 * Copyright 2013-2017 the original author or authors.
 *
 * Licensed under the Apache License, Version 2.0 (the "License");
 * you may not use this file except in compliance with the License.
 * You may obtain a copy of the License at
 *
 *      http://www.apache.org/licenses/LICENSE-2.0
 *
 * Unless required by applicable law or agreed to in writing, software
 * distributed under the License is distributed on an "AS IS" BASIS,
 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 * See the License for the specific language governing permissions and
 * limitations under the License.
 *
 */

package org.springframework.cloud.gateway.handler;

import java.util.ArrayList;
import java.util.Collection;
import java.util.Collections;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
import java.util.Optional;
import java.util.concurrent.ConcurrentHashMap;
import java.util.concurrent.ConcurrentMap;
import java.util.stream.Collectors;

import org.apache.commons.logging.Log;
import org.apache.commons.logging.LogFactory;
import org.springframework.cloud.gateway.filter.factory.WebFilterFactory;
import org.springframework.cloud.gateway.model.FilterDefinition;
import org.springframework.cloud.gateway.model.Route;
import org.springframework.cloud.gateway.config.GatewayProperties;
import org.springframework.cloud.gateway.filter.GlobalFilter;
import org.springframework.cloud.gateway.support.NameUtils;
import org.springframework.cloud.gateway.support.RefreshRoutesEvent;
import org.springframework.context.event.EventListener;
import org.springframework.core.Ordered;
import org.springframework.core.annotation.AnnotationAwareOrderComparator;
import org.springframework.tuple.Tuple;
import org.springframework.tuple.TupleBuilder;
import org.springframework.web.server.ServerWebExchange;
import org.springframework.web.server.WebFilter;
import org.springframework.web.server.WebFilterChain;
import org.springframework.web.server.WebHandler;
import org.springframework.web.server.handler.WebHandlerDecorator;

import static java.util.Collections.emptyList;
import static org.springframework.cloud.gateway.support.ServerWebExchangeUtils.GATEWAY_ROUTE_ATTR;

import reactor.core.publisher.Mono;

/**
 * WebHandler that delegates to a chain of {@link GlobalFilter} instances and
 * {@link WebFilterFactory} instances then to the target {@link WebHandler}.
 *
 * @author <NAME>
 * @author <NAME>
 * @since 0.1
 */
public class FilteringWebHandler extends WebHandlerDecorator {
	protected final Log logger = LogFactory.getLog(getClass());

	private final GatewayProperties gatewayProperties;
	private final List<GlobalFilter> globalFilters;
	private final Map<String, WebFilterFactory> webFilterFactories = new HashMap<>();

	private final ConcurrentMap<String, List<WebFilter>> combinedFiltersForRoute = new ConcurrentHashMap<>();

	public FilteringWebHandler(GatewayProperties gatewayProperties, List<GlobalFilter> globalFilters,
							   List<WebFilterFactory> webFilterFactories) {
		this(new EmptyWebHandler(), gatewayProperties, globalFilters, webFilterFactories);
	}

	public FilteringWebHandler(WebHandler targetHandler, GatewayProperties gatewayProperties, List<GlobalFilter> globalFilters,
							   List<WebFilterFactory> webFilterFactories) {
		super(targetHandler);
		this.gatewayProperties = gatewayProperties;
		this.globalFilters = initList(globalFilters);
		webFilterFactories.forEach(factory -> this.webFilterFactories.put(factory.name(), factory));
	}

	private static <T> List<T> initList(List<T> list) {
		return (list != null ? list : emptyList());
	}

	/**
	 * Return a read-only list of the configured globalFilters.
	 */
	public List<GlobalFilter> getGlobalFilters() {
		return this.globalFilters;
	}


    @EventListener(RefreshRoutesEvent.class)
    /* for testing */ void handleRefresh() {
        this.combinedFiltersForRoute.clear();
    }

	@Override
	public Mono<Void> handle(ServerWebExchange exchange) {
		Optional<Route> route = exchange.getAttribute(GATEWAY_ROUTE_ATTR);
		List<WebFilter> webFilters = combineFiltersForRoute(route);

		logger.debug("Sorted webFilterFactories: "+ webFilters);

		return new DefaultWebFilterChain(webFilters, getDelegate()).filter(exchange);
	}

	public List<WebFilter> combineFiltersForRoute(Optional<Route> route) {
		if (!route.isPresent()) {
			return Collections.emptyList();
		}
		List<WebFilter> combinedFilters = this.combinedFiltersForRoute.get(route.get().getId());
		if (combinedFilters == null) {

			//TODO: probably a java 8 stream way of doing this
			combinedFilters = new ArrayList<>(loadFilters(this.globalFilters));

			//TODO: support option to apply defaults after route specific filters?
			if (!this.gatewayProperties.getDefaultFilters().isEmpty()) {
				combinedFilters.addAll(loadWebFilters("defaultFilters",
						this.gatewayProperties.getDefaultFilters()));
			}

			if (route.isPresent() && !route.get().getFilters().isEmpty()) {
				combinedFilters.addAll(loadWebFilters(route.get().getId(), route.get().getFilters()));
			}

			AnnotationAwareOrderComparator.sort(combinedFilters);
			this.combinedFiltersForRoute.put(route.get().getId(), combinedFilters);
		}
		return combinedFilters;
	}

	private Collection<WebFilter> loadFilters(List<GlobalFilter> filters) {
		return filters.stream()
				.map(filter -> {
					WebFilterAdapter webFilter = new WebFilterAdapter(filter);
					if (filter instanceof Ordered) {
						int order = ((Ordered) filter).getOrder();
						return new OrderedWebFilter(webFilter, order);
					}
					return webFilter;
				}).collect(Collectors.toList());
	}

	private List<WebFilter> loadWebFilters(String id, List<FilterDefinition> filterDefinitions) {
		List<WebFilter> filters = filterDefinitions.stream()
				.map(definition -> {
					WebFilterFactory filter = this.webFilterFactories.get(definition.getName());
					if (filter == null) {
						throw new IllegalArgumentException("Unable to find WebFilterFactory with name " + definition.getName());<commit_msg>Change initializing map to use `computeIfAbsent`<commit_after>/*
 * Copyright 2013-2017 the original author or authors.
 *
 * Licensed under the Apache License, Version 2.0 (the "License");
 * you may not use this file except in compliance with the License.
 * You may obtain a copy of the License at
 *
 *      http://www.apache.org/licenses/LICENSE-2.0
 *
 * Unless required by applicable law or agreed to in writing, software
 * distributed under the License is distributed on an "AS IS" BASIS,
 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 * See the License for the specific language governing permissions and
 * limitations under the License.
 *
 */

package org.springframework.cloud.gateway.handler;

import java.util.ArrayList;
import java.util.Collection;
import java.util.Collections;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
import java.util.Optional;
import java.util.concurrent.ConcurrentHashMap;
import java.util.concurrent.ConcurrentMap;
import java.util.stream.Collectors;

import org.apache.commons.logging.Log;
import org.apache.commons.logging.LogFactory;
import org.springframework.cloud.gateway.filter.factory.WebFilterFactory;
import org.springframework.cloud.gateway.model.FilterDefinition;
import org.springframework.cloud.gateway.model.Route;
import org.springframework.cloud.gateway.config.GatewayProperties;
import org.springframework.cloud.gateway.filter.GlobalFilter;
import org.springframework.cloud.gateway.support.NameUtils;
import org.springframework.cloud.gateway.support.RefreshRoutesEvent;
import org.springframework.context.event.EventListener;
import org.springframework.core.Ordered;
import org.springframework.core.annotation.AnnotationAwareOrderComparator;
import org.springframework.tuple.Tuple;
import org.springframework.tuple.TupleBuilder;
import org.springframework.web.server.ServerWebExchange;
import org.springframework.web.server.WebFilter;
import org.springframework.web.server.WebFilterChain;
import org.springframework.web.server.WebHandler;
import org.springframework.web.server.handler.WebHandlerDecorator;

import static java.util.Collections.emptyList;
import static org.springframework.cloud.gateway.support.ServerWebExchangeUtils.GATEWAY_ROUTE_ATTR;

import reactor.core.publisher.Mono;

/**
 * WebHandler that delegates to a chain of {@link GlobalFilter} instances and
 * {@link WebFilterFactory} instances then to the target {@link WebHandler}.
 *
 * @author <NAME>
 * @author <NAME>
 * @since 0.1
 */
public class FilteringWebHandler extends WebHandlerDecorator {
	protected final Log logger = LogFactory.getLog(getClass());

	private final GatewayProperties gatewayProperties;
	private final List<GlobalFilter> globalFilters;
	private final Map<String, WebFilterFactory> webFilterFactories = new HashMap<>();

	private final ConcurrentMap<String, List<WebFilter>> combinedFiltersForRoute = new ConcurrentHashMap<>();

	public FilteringWebHandler(GatewayProperties gatewayProperties, List<GlobalFilter> globalFilters,
							   List<WebFilterFactory> webFilterFactories) {
		this(new EmptyWebHandler(), gatewayProperties, globalFilters, webFilterFactories);
	}

	public FilteringWebHandler(WebHandler targetHandler, GatewayProperties gatewayProperties, List<GlobalFilter> globalFilters,
							   List<WebFilterFactory> webFilterFactories) {
		super(targetHandler);
		this.gatewayProperties = gatewayProperties;
		this.globalFilters = initList(globalFilters);
		webFilterFactories.forEach(factory -> this.webFilterFactories.put(factory.name(), factory));
	}

	private static <T> List<T> initList(List<T> list) {
		return (list != null ? list : emptyList());
	}

	/**
	 * Return a read-only list of the configured globalFilters.
	 */
	public List<GlobalFilter> getGlobalFilters() {
		return this.globalFilters;
	}


    @EventListener(RefreshRoutesEvent.class)
    /* for testing */ void handleRefresh() {
        this.combinedFiltersForRoute.clear();
    }

	@Override
	public Mono<Void> handle(ServerWebExchange exchange) {
		Optional<Route> route = exchange.getAttribute(GATEWAY_ROUTE_ATTR);
		List<WebFilter> webFilters = combineFiltersForRoute(route);

		logger.debug("Sorted webFilterFactories: "+ webFilters);

		return new DefaultWebFilterChain(webFilters, getDelegate()).filter(exchange);
	}

	public List<WebFilter> combineFiltersForRoute(Optional<Route> route) {
		if (!route.isPresent()) {
			return Collections.emptyList();
		}
		List<WebFilter> combinedFilters = this.combinedFiltersForRoute.computeIfAbsent(
				route.get().getId(), s -> {

			//TODO: probably a java 8 stream way of doing this
			List<WebFilter> combined = new ArrayList<>(loadFilters(this.globalFilters));

			//TODO: support option to apply defaults after route specific filters?
			if (!this.gatewayProperties.getDefaultFilters().isEmpty()) {
				combined.addAll(loadWebFilters("defaultFilters",
						this.gatewayProperties.getDefaultFilters()));
			}

			if (route.isPresent() && !route.get().getFilters().isEmpty()) {
				combined.addAll(loadWebFilters(route.get().getId(), route.get().getFilters()));
			}

			AnnotationAwareOrderComparator.sort(combined);
			return combined;
		});
		return combinedFilters;
	}

	private Collection<WebFilter> loadFilters(List<GlobalFilter> filters) {
		return filters.stream()
				.map(filter -> {
					WebFilterAdapter webFilter = new WebFilterAdapter(filter);
					if (filter instanceof Ordered) {
						int order = ((Ordered) filter).getOrder();
						return new OrderedWebFilter(webFilter, order);
					}
					return webFilter;
				}).collect(Collectors.toList());
	}

	private List<WebFilter> loadWebFilters(String id, List<FilterDefinition> filterDefinitions) {
		List<WebFilter> filters = filterDefinitions.stream()
				.map(definition -> {
					WebFilterFactory filter = this.webFilterFactories.get(definition.getName());
					if (filter == null) {
						throw new IllegalArgumentException("Unable to find WebFilterFactory with name " + definition.getName());
{'fraction_non_alphanumeric': 0.10374544249254226, 'fraction_numerical': 0.002485913158766987, 'mean_word_length': 5.409453000531068, 'pattern_counts': {'":': 0, '<': 60, '<?xml version=': 0, '>': 67, 'https://': 0, 'lorem ipsum': 0, 'www.': 2, 'xml': 0}, 'pii_count': 0, 'substrings_counts': 0, 'word_list_counts': {'cursed_substrings.json': 0, 'profanity_word_list.json': 0, 'sexual_word_list.json': 0, 'zh_pornsignals.json': 0}}
{'dir': 'git-commits-cleaned', 'id': '6822112', 'n_tokens_mistral': 3501, 'n_tokens_neox': 3288, 'n_words': 786}