/*
 * Copyright 2002-2015 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.web.reactive;

import java.util.ArrayList;
import java.util.List;
import java.util.Map;

import org.apache.commons.logging.Log;
import org.apache.commons.logging.LogFactory;
import reactor.core.publisher.Flux;
import reactor.core.publisher.Mono;

import org.springframework.beans.BeansException;
import org.springframework.beans.factory.BeanFactoryUtils;
import org.springframework.context.ApplicationContext;
import org.springframework.context.ApplicationContextAware;
import org.springframework.core.annotation.AnnotationAwareOrderComparator;
import org.springframework.http.HttpStatus;
import org.springframework.http.server.reactive.ServerHttpRequest;
import org.springframework.web.server.ResponseStatusException;
import org.springframework.web.server.ServerWebExchange;
import org.springframework.web.server.WebHandler;

/**
 * Central dispatcher for HTTP request handlers/controllers. Dispatches to registered
 * handlers for processing a web request, providing convenient mapping facilities.
 *
 * <p>It can use any {@link HandlerMapping} implementation to control the routing of
 * requests to handler objects. HandlerMapping objects can be defined as beans in
 * the application context.
 *
 * <p>It can use any {@link HandlerAdapter}; this allows for using any handler interface.
 * HandlerAdapter objects can be added as beans in the application context.
 *
 * <p>It can use any {@link HandlerResultHandler}; this allows to process the result of
 * the request handling. HandlerResultHandler objects can be added as beans in the
 * application context.
 *
 * @author Rossen Stoyanchev
 * @author Sebastien Deleuze
 */
public class DispatcherHandler implements WebHandler, ApplicationContextAware {

	private static final Log logger = LogFactory.getLog(DispatcherHandler.class);


	private List<HandlerMapping> handlerMappings;

	private List<HandlerAdapter> handlerAdapters;

	private List<HandlerResultHandler> resultHandlers;


	@Override
	public void setApplicationContext(ApplicationContext applicationContext) throws BeansException {
		initStrategies(applicationContext);
	}


	protected void initStrategies(ApplicationContext context) {

		Map<String, HandlerMapping> mappingBeans = BeanFactoryUtils.beansOfTypeIncludingAncestors(
				context, HandlerMapping.class, true, false);

		this.handlerMappings = new ArrayList<>(mappingBeans.values());
		this.handlerMappings.add(new NotFoundHandlerMapping());
		AnnotationAwareOrderComparator.sort(this.handlerMappings);

		Map<String, HandlerAdapter> adapterBeans = BeanFactoryUtils.beansOfTypeIncludingAncestors(
				context, HandlerAdapter.class, true, false);

		this.handlerAdapters = new ArrayList<>(adapterBeans.values());
		AnnotationAwareOrderComparator.sort(this.handlerAdapters);

		Map<String, HandlerResultHandler> beans = BeanFactoryUtils.beansOfTypeIncludingAncestors(
				context, HandlerResultHandler.class, true, false);

		this.resultHandlers = new ArrayList<>(beans.values());
		AnnotationAwareOrderComparator.sort(this.resultHandlers);
	}


	@Override
	public Mono<Void> handle(ServerWebExchange exchange) {
		if (logger.isDebugEnabled()) {
			ServerHttpRequest request = exchange.getRequest();
			logger.debug("Processing " + request.getMethod() + " request for [" + request.getURI() + "]");
		}
		return Flux.fromIterable(this.handlerMappings)
				.concatMap(mapping -> mapping.getHandler(exchange))
				.next()
				.then(handler -> invokeHandler(exchange, handler))
				.then(result -> handleResult(exchange, result));
	}

	private Mono<HandlerResult> invokeHandler(ServerWebExchange exchange, Object handler) {
		for (HandlerAdapter handlerAdapter : this.handlerAdapters) {
			if (handlerAdapter.supports(handler)) {
				return handlerAdapter.handle(exchange, handler);
			}
		}
		return Mono.error(new IllegalStateException("No HandlerAdapter: " + handler));
	}

	private Mono<Void> handleResult(ServerWebExchange exchange, HandlerResult result) {
		return getResultHandler(result).handleResult(exchange, result)
				.otherwise(ex -> result.applyExceptionHandler(ex).then(exceptionResult ->
						getResultHandler(result).handleResult(exchange, exceptionResult)));
	}

	private HandlerResultHandler getResultHandler(HandlerResult handlerResult) {
		for (HandlerResultHandler resultHandler : resultHandlers) {
			if (resultHandler.supports(handlerResult)) {
				return resultHandler;
			}
		}
		throw new IllegalStateException("No HandlerResultHandler for " + handlerResult.getReturnValue());
	}


	private static class NotFoundHandlerMapping implements HandlerMapping {

		@SuppressWarnings("ThrowableInstanceNeverThrown")
		private static final Exception HANDLER_NOT_FOUND_EXCEPTION =
				new ResponseStatusException(HttpStatus.NOT_FOUND, "No matching handler");


		@Override
		public Mono<Object> getHandler(ServerWebExchange exchange) {
			return Mono.error(HANDLER_NOT_FOUND_EXCEPTION);
		}
	}

}
@@ -2434,16 +2434,207 @@
 lass);%0A%0A
+%09@SuppressWarnings(%22ThrowableInstanceNeverThrown%22)%0A%09private static final Exception HANDLER_NOT_FOUND_EXCEPTION =%0A%09%09%09new ResponseStatusException(HttpStatus.NOT_FOUND, %22No matching handler%22);%0A%0A
 %0A%09privat
@@ -3202,66 +3202,8 @@
 ));%0A
-%09%09this.handlerMappings.add(new NotFoundHandlerMapping());%0A
 %09%09An
@@ -4173,16 +4173,79 @@
 .next()%0A
+%09%09%09%09.otherwiseIfEmpty(Mono.error(HANDLER_NOT_FOUND_EXCEPTION))%0A
 %09%09%09%09.the
@@ -5219,16 +5219,16 @@
 %09%09%7D%0A%09%09%7D%0A
+
 %09%09throw 
@@ -5327,409 +5327,5 @@
 %09%7D%0A%0A
-%0A%09private static class NotFoundHandlerMapping implements HandlerMapping %7B%0A%0A%09%09@SuppressWarnings(%22ThrowableInstanceNeverThrown%22)%0A%09%09private static final Exception HANDLER_NOT_FOUND_EXCEPTION =%0A%09%09%09%09new ResponseStatusException(HttpStatus.NOT_FOUND, %22No matching handler%22);%0A%0A%0A%09%09@Override%0A%09%09public Mono%3CObject%3E getHandler(ServerWebExchange exchange) %7B%0A%09%09%09return Mono.error(HANDLER_NOT_FOUND_EXCEPTION);%0A%09%09%7D%0A%09%7D%0A%0A
 %7D

Improve 404 "handler not found" handling