diff --git a/spring-webflux/src/main/java/org/springframework/web/reactive/function/server/PathResourceLookupFunction.java b/spring-webflux/src/main/java/org/springframework/web/reactive/function/server/PathResourceLookupFunction.java index c422ead366..6fdce491da 100644 --- a/spring-webflux/src/main/java/org/springframework/web/reactive/function/server/PathResourceLookupFunction.java +++ b/spring-webflux/src/main/java/org/springframework/web/reactive/function/server/PathResourceLookupFunction.java @@ -155,4 +155,8 @@ class PathResourceLookupFunction implements Function %s", this.pattern, this.location); + } } diff --git a/spring-webflux/src/main/java/org/springframework/web/reactive/function/server/RouterFunction.java b/spring-webflux/src/main/java/org/springframework/web/reactive/function/server/RouterFunction.java index 4f7c012300..1326cec6ec 100644 --- a/spring-webflux/src/main/java/org/springframework/web/reactive/function/server/RouterFunction.java +++ b/spring-webflux/src/main/java/org/springframework/web/reactive/function/server/RouterFunction.java @@ -47,8 +47,7 @@ public interface RouterFunction { * @see #andOther(RouterFunction) */ default RouterFunction and(RouterFunction other) { - return request -> this.route(request) - .switchIfEmpty(Mono.defer(() -> other.route(request))); + return new RouterFunctions.SameComposedRouterFunction<>(this, other); } /** @@ -61,10 +60,7 @@ public interface RouterFunction { * @see #and(RouterFunction) */ default RouterFunction andOther(RouterFunction other) { - return request -> this.route(request) - .map(RouterFunctions::cast) - .switchIfEmpty( - Mono.defer(() -> other.route(request).map(RouterFunctions::cast))); + return new RouterFunctions.DifferentComposedRouterFunction(this, other); } /** @@ -105,7 +101,18 @@ public interface RouterFunction { * @return the filtered routing function */ default RouterFunction filter(HandlerFilterFunction filterFunction) { - return request -> this.route(request).map(filterFunction::apply); + return new RouterFunctions.FilteredRouterFunction<>(this, filterFunction); + } + + /** + * Accept the given visitor. Default implementation calls + * {@link RouterFunctions.Visitor#unknown(RouterFunction)}; composed {@code RouterFunction} + * implementations are expected to call {@code accept} for all components that make up this + * router function + * @param visitor the visitor to accept + */ + default void accept(RouterFunctions.Visitor visitor) { + visitor.unknown(this); } } diff --git a/spring-webflux/src/main/java/org/springframework/web/reactive/function/server/RouterFunctions.java b/spring-webflux/src/main/java/org/springframework/web/reactive/function/server/RouterFunctions.java index 029794636c..a20c1e17c8 100644 --- a/spring-webflux/src/main/java/org/springframework/web/reactive/function/server/RouterFunctions.java +++ b/spring-webflux/src/main/java/org/springframework/web/reactive/function/server/RouterFunctions.java @@ -150,7 +150,7 @@ public abstract class RouterFunctions { */ public static RouterFunction resources(Function> lookupFunction) { Assert.notNull(lookupFunction, "'lookupFunction' must not be null"); - return request -> lookupFunction.apply(request).map(ResourceHandlerFunction::new); + return new ResourcesRouterFunction(lookupFunction); } /** @@ -259,8 +259,138 @@ public abstract class RouterFunctions { return (HandlerFunction) handlerFunction; } - private static class DefaultRouterFunction - implements RouterFunction { + + /** + * Receives notifications from the logical structure of router functions. + */ + public interface Visitor { + + /** + * Receive notification of the beginning of a nested router function. + * @param predicate the predicate that applies to the nested router functions + * @see RouterFunctions#nest(RequestPredicate, RouterFunction) + */ + void startNested(RequestPredicate predicate); + + /** + * Receive notification of the end of a nested router function. + * @param predicate the predicate that applies to the nested router functions + * @see RouterFunctions#nest(RequestPredicate, RouterFunction) + */ + void endNested(RequestPredicate predicate); + + /** + * Receive notification of a standard predicated route to a handler function. + * @param predicate the predicate that applies to the handler function + * @param handlerFunction the handler function. + * @see RouterFunctions#route(RequestPredicate, HandlerFunction) + */ + void route(RequestPredicate predicate, HandlerFunction handlerFunction); + + /** + * Receive notification of a resource router function. + * @param lookupFunction the lookup function for the resources + * @see RouterFunctions#resources(Function) + */ + void resources(Function> lookupFunction); + + /** + * Receive notification of an unknown router function. This method is called for router + * functions that were not created via the various {@link RouterFunctions} methods. + * @param routerFunction the router function + */ + void unknown(RouterFunction routerFunction); + } + + + private static abstract class AbstractRouterFunction implements RouterFunction { + + @Override + public String toString() { + ToStringVisitor visitor = new ToStringVisitor(); + accept(visitor); + return visitor.toString(); + } + } + + final static class SameComposedRouterFunction extends AbstractRouterFunction { + + private final RouterFunction first; + + private final RouterFunction second; + + public SameComposedRouterFunction(RouterFunction first, RouterFunction second) { + this.first = first; + this.second = second; + } + + @Override + public Mono> route(ServerRequest request) { + return this.first.route(request) + .switchIfEmpty(Mono.defer(() -> this.second.route(request))); + } + + @Override + public void accept(Visitor visitor) { + this.first.accept(visitor); + this.second.accept(visitor); + } + } + + final static class DifferentComposedRouterFunction extends AbstractRouterFunction { + + private final RouterFunction first; + + private final RouterFunction second; + + public DifferentComposedRouterFunction(RouterFunction first, RouterFunction second) { + this.first = first; + this.second = second; + } + + @Override + public Mono> route(ServerRequest request) { + return this.first.route(request) + .map(RouterFunctions::cast) + .switchIfEmpty(Mono.defer(() -> this.second.route(request).map(RouterFunctions::cast))); + } + + @Override + public void accept(Visitor visitor) { + this.first.accept(visitor); + this.second.accept(visitor); + } + + } + + final static class FilteredRouterFunction + implements RouterFunction { + + private final RouterFunction routerFunction; + + private final HandlerFilterFunction filterFunction; + + public FilteredRouterFunction( + RouterFunction routerFunction, + HandlerFilterFunction filterFunction) { + this.routerFunction = routerFunction; + this.filterFunction = filterFunction; + } + + @Override + public Mono> route(ServerRequest request) { + return this.routerFunction.route(request).map(this.filterFunction::apply); + } + + @Override + public void accept(Visitor visitor) { + this.routerFunction.accept(visitor); + } + + } + + private static final class DefaultRouterFunction + extends AbstractRouterFunction { private final RequestPredicate predicate; @@ -287,13 +417,14 @@ public abstract class RouterFunctions { } @Override - public String toString() { - return String.format("%s -> %s", this.predicate, this.handlerFunction); + public void accept(Visitor visitor) { + visitor.route(this.predicate, this.handlerFunction); } + } - private static class DefaultNestedRouterFunction - implements RouterFunction { + private static final class DefaultNestedRouterFunction + extends AbstractRouterFunction { private final RequestPredicate predicate; @@ -322,12 +453,33 @@ public abstract class RouterFunctions { } @Override - public String toString() { - return String.format("%s -> %s", this.predicate, this.routerFunction); + public void accept(Visitor visitor) { + visitor.startNested(this.predicate); + this.routerFunction.accept(visitor); + visitor.endNested(this.predicate); } } + private static class ResourcesRouterFunction extends AbstractRouterFunction { + + private final Function> lookupFunction; + + public ResourcesRouterFunction(Function> lookupFunction) { + this.lookupFunction = lookupFunction; + } + + @Override + public Mono> route(ServerRequest request) { + return this.lookupFunction.apply(request).map(ResourceHandlerFunction::new); + } + + @Override + public void accept(Visitor visitor) { + visitor.resources(this.lookupFunction); + } + } + private static class HandlerStrategiesResponseContext implements ServerResponse.Context { private final HandlerStrategies strategies; @@ -346,4 +498,5 @@ public abstract class RouterFunctions { return this.strategies.viewResolvers(); } } + } diff --git a/spring-webflux/src/main/java/org/springframework/web/reactive/function/server/ToStringVisitor.java b/spring-webflux/src/main/java/org/springframework/web/reactive/function/server/ToStringVisitor.java new file mode 100644 index 0000000000..1ded643430 --- /dev/null +++ b/spring-webflux/src/main/java/org/springframework/web/reactive/function/server/ToStringVisitor.java @@ -0,0 +1,91 @@ +/* + * Copyright 2002-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.web.reactive.function.server; + +import java.util.function.Function; + +import reactor.core.publisher.Mono; + +import org.springframework.core.io.Resource; + +/** + * Implementation of {@link RouterFunctions.Visitor} that creates a + * @author Arjen Poutsma + * @since 5.0 + */ +class ToStringVisitor implements RouterFunctions.Visitor { + + private static final String NEW_LINE = System.getProperty("line.separator", "\\n"); + + private final StringBuilder builder = new StringBuilder(); + + private int indent = 0; + + @Override + public void startNested(RequestPredicate predicate) { + indent(); + this.builder.append(predicate); + this.builder.append(" => {"); + this.builder.append(NEW_LINE); + this.indent++; + } + + @Override + public void endNested(RequestPredicate predicate) { + this.indent--; + indent(); + this.builder.append('}'); + this.builder.append(NEW_LINE); + } + + @Override + public void route(RequestPredicate predicate, HandlerFunction handlerFunction) { + indent(); + this.builder.append(predicate); + this.builder.append(" -> "); + this.builder.append(handlerFunction); + this.builder.append(NEW_LINE); + } + + @Override + public void resources(Function> lookupFunction) { + indent(); + this.builder.append(lookupFunction); + this.builder.append(NEW_LINE); + } + + @Override + public void unknown(RouterFunction routerFunction) { + indent(); + this.builder.append(routerFunction); + } + + private void indent() { + for (int i=0; i < this.indent; i++) { + this.builder.append(' '); + } + } + + @Override + public String toString() { + String result = this.builder.toString(); + if (result.endsWith(NEW_LINE)) { + result = result.substring(0, result.length() - NEW_LINE.length()); + } + return result; + } +} diff --git a/spring-webflux/src/main/java/org/springframework/web/reactive/function/server/support/RouterFunctionMapping.java b/spring-webflux/src/main/java/org/springframework/web/reactive/function/server/support/RouterFunctionMapping.java index 016162414f..63c6a4f037 100644 --- a/spring-webflux/src/main/java/org/springframework/web/reactive/function/server/support/RouterFunctionMapping.java +++ b/spring-webflux/src/main/java/org/springframework/web/reactive/function/server/support/RouterFunctionMapping.java @@ -102,8 +102,8 @@ public class RouterFunctionMapping extends AbstractHandlerMapping implements Ini List> routerFunctions = routerFunctions(); if (!CollectionUtils.isEmpty(routerFunctions) && logger.isInfoEnabled()) { - routerFunctions.forEach(routerFunction1 -> { - logger.info("Mapped " + routerFunction1); + routerFunctions.forEach(routerFunction -> { + logger.info("Mapped " + routerFunction); }); } this.routerFunction = routerFunctions.stream()