Introduce RouterFunction visitor

This commit introduces a visitor for router functions
(RouterFunctions.Visitor), allowing to iterate over all the components
that make up a router function.

This commit also introduces a ToStringVisitor, which creates a nicely
formatted string for use with toString().

Issue: SPR-15711, SPR-15711
This commit is contained in:
Arjen Poutsma 2017-09-22 12:19:34 +02:00
parent 7b6f1d1b58
commit 2841ef5d05
5 changed files with 273 additions and 18 deletions

View File

@ -155,4 +155,8 @@ class PathResourceLookupFunction implements Function<ServerRequest, Mono<Resourc
return true;
}
@Override
public String toString() {
return String.format("%s -> %s", this.pattern, this.location);
}
}

View File

@ -47,8 +47,7 @@ public interface RouterFunction<T extends ServerResponse> {
* @see #andOther(RouterFunction)
*/
default RouterFunction<T> and(RouterFunction<T> 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<T extends ServerResponse> {
* @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<T extends ServerResponse> {
* @return the filtered routing function
*/
default <S extends ServerResponse> RouterFunction<S> filter(HandlerFilterFunction<T, S> 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);
}
}

View File

@ -150,7 +150,7 @@ public abstract class RouterFunctions {
*/
public static RouterFunction<ServerResponse> resources(Function<ServerRequest, Mono<Resource>> 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<T>) handlerFunction;
}
private static class DefaultRouterFunction<T extends ServerResponse>
implements RouterFunction<T> {
/**
* 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<ServerRequest, Mono<Resource>> 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<T extends ServerResponse> implements RouterFunction<T> {
@Override
public String toString() {
ToStringVisitor visitor = new ToStringVisitor();
accept(visitor);
return visitor.toString();
}
}
final static class SameComposedRouterFunction<T extends ServerResponse> extends AbstractRouterFunction<T> {
private final RouterFunction<T> first;
private final RouterFunction<T> second;
public SameComposedRouterFunction(RouterFunction<T> first, RouterFunction<T> second) {
this.first = first;
this.second = second;
}
@Override
public Mono<HandlerFunction<T>> 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<ServerResponse> {
private final RouterFunction<?> first;
private final RouterFunction<?> second;
public DifferentComposedRouterFunction(RouterFunction<?> first, RouterFunction<?> second) {
this.first = first;
this.second = second;
}
@Override
public Mono<HandlerFunction<ServerResponse>> 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<T extends ServerResponse, S extends ServerResponse>
implements RouterFunction<S> {
private final RouterFunction<T> routerFunction;
private final HandlerFilterFunction<T, S> filterFunction;
public FilteredRouterFunction(
RouterFunction<T> routerFunction,
HandlerFilterFunction<T, S> filterFunction) {
this.routerFunction = routerFunction;
this.filterFunction = filterFunction;
}
@Override
public Mono<HandlerFunction<S>> 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<T extends ServerResponse>
extends AbstractRouterFunction<T> {
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<T extends ServerResponse>
implements RouterFunction<T> {
private static final class DefaultNestedRouterFunction<T extends ServerResponse>
extends AbstractRouterFunction<T> {
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<ServerResponse> {
private final Function<ServerRequest, Mono<Resource>> lookupFunction;
public ResourcesRouterFunction(Function<ServerRequest, Mono<Resource>> lookupFunction) {
this.lookupFunction = lookupFunction;
}
@Override
public Mono<HandlerFunction<ServerResponse>> 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();
}
}
}

View File

@ -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<ServerRequest, Mono<Resource>> 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;
}
}

View File

@ -102,8 +102,8 @@ public class RouterFunctionMapping extends AbstractHandlerMapping implements Ini
List<RouterFunction<?>> 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()