Skip to content

Commit 2841ef5

Browse files
committed
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
1 parent 7b6f1d1 commit 2841ef5

File tree

5 files changed

+273
-18
lines changed

5 files changed

+273
-18
lines changed

spring-webflux/src/main/java/org/springframework/web/reactive/function/server/PathResourceLookupFunction.java

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -155,4 +155,8 @@ else if (resource instanceof ClassPathResource) {
155155
return true;
156156
}
157157

158+
@Override
159+
public String toString() {
160+
return String.format("%s -> %s", this.pattern, this.location);
161+
}
158162
}

spring-webflux/src/main/java/org/springframework/web/reactive/function/server/RouterFunction.java

Lines changed: 14 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -47,8 +47,7 @@ public interface RouterFunction<T extends ServerResponse> {
4747
* @see #andOther(RouterFunction)
4848
*/
4949
default RouterFunction<T> and(RouterFunction<T> other) {
50-
return request -> this.route(request)
51-
.switchIfEmpty(Mono.defer(() -> other.route(request)));
50+
return new RouterFunctions.SameComposedRouterFunction<>(this, other);
5251
}
5352

5453
/**
@@ -61,10 +60,7 @@ default RouterFunction<T> and(RouterFunction<T> other) {
6160
* @see #and(RouterFunction)
6261
*/
6362
default RouterFunction<?> andOther(RouterFunction<?> other) {
64-
return request -> this.route(request)
65-
.map(RouterFunctions::cast)
66-
.switchIfEmpty(
67-
Mono.defer(() -> other.route(request).map(RouterFunctions::cast)));
63+
return new RouterFunctions.DifferentComposedRouterFunction(this, other);
6864
}
6965

7066
/**
@@ -105,7 +101,18 @@ default RouterFunction<T> andNest(RequestPredicate predicate, RouterFunction<T>
105101
* @return the filtered routing function
106102
*/
107103
default <S extends ServerResponse> RouterFunction<S> filter(HandlerFilterFunction<T, S> filterFunction) {
108-
return request -> this.route(request).map(filterFunction::apply);
104+
return new RouterFunctions.FilteredRouterFunction<>(this, filterFunction);
105+
}
106+
107+
/**
108+
* Accept the given visitor. Default implementation calls
109+
* {@link RouterFunctions.Visitor#unknown(RouterFunction)}; composed {@code RouterFunction}
110+
* implementations are expected to call {@code accept} for all components that make up this
111+
* router function
112+
* @param visitor the visitor to accept
113+
*/
114+
default void accept(RouterFunctions.Visitor visitor) {
115+
visitor.unknown(this);
109116
}
110117

111118
}

spring-webflux/src/main/java/org/springframework/web/reactive/function/server/RouterFunctions.java

Lines changed: 162 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -150,7 +150,7 @@ public static RouterFunction<ServerResponse> resources(String pattern, Resource
150150
*/
151151
public static RouterFunction<ServerResponse> resources(Function<ServerRequest, Mono<Resource>> lookupFunction) {
152152
Assert.notNull(lookupFunction, "'lookupFunction' must not be null");
153-
return request -> lookupFunction.apply(request).map(ResourceHandlerFunction::new);
153+
return new ResourcesRouterFunction(lookupFunction);
154154
}
155155

156156
/**
@@ -259,8 +259,138 @@ static <T extends ServerResponse> HandlerFunction<T> cast(HandlerFunction<?> han
259259
return (HandlerFunction<T>) handlerFunction;
260260
}
261261

262-
private static class DefaultRouterFunction<T extends ServerResponse>
263-
implements RouterFunction<T> {
262+
263+
/**
264+
* Receives notifications from the logical structure of router functions.
265+
*/
266+
public interface Visitor {
267+
268+
/**
269+
* Receive notification of the beginning of a nested router function.
270+
* @param predicate the predicate that applies to the nested router functions
271+
* @see RouterFunctions#nest(RequestPredicate, RouterFunction)
272+
*/
273+
void startNested(RequestPredicate predicate);
274+
275+
/**
276+
* Receive notification of the end of a nested router function.
277+
* @param predicate the predicate that applies to the nested router functions
278+
* @see RouterFunctions#nest(RequestPredicate, RouterFunction)
279+
*/
280+
void endNested(RequestPredicate predicate);
281+
282+
/**
283+
* Receive notification of a standard predicated route to a handler function.
284+
* @param predicate the predicate that applies to the handler function
285+
* @param handlerFunction the handler function.
286+
* @see RouterFunctions#route(RequestPredicate, HandlerFunction)
287+
*/
288+
void route(RequestPredicate predicate, HandlerFunction<?> handlerFunction);
289+
290+
/**
291+
* Receive notification of a resource router function.
292+
* @param lookupFunction the lookup function for the resources
293+
* @see RouterFunctions#resources(Function)
294+
*/
295+
void resources(Function<ServerRequest, Mono<Resource>> lookupFunction);
296+
297+
/**
298+
* Receive notification of an unknown router function. This method is called for router
299+
* functions that were not created via the various {@link RouterFunctions} methods.
300+
* @param routerFunction the router function
301+
*/
302+
void unknown(RouterFunction<?> routerFunction);
303+
}
304+
305+
306+
private static abstract class AbstractRouterFunction<T extends ServerResponse> implements RouterFunction<T> {
307+
308+
@Override
309+
public String toString() {
310+
ToStringVisitor visitor = new ToStringVisitor();
311+
accept(visitor);
312+
return visitor.toString();
313+
}
314+
}
315+
316+
final static class SameComposedRouterFunction<T extends ServerResponse> extends AbstractRouterFunction<T> {
317+
318+
private final RouterFunction<T> first;
319+
320+
private final RouterFunction<T> second;
321+
322+
public SameComposedRouterFunction(RouterFunction<T> first, RouterFunction<T> second) {
323+
this.first = first;
324+
this.second = second;
325+
}
326+
327+
@Override
328+
public Mono<HandlerFunction<T>> route(ServerRequest request) {
329+
return this.first.route(request)
330+
.switchIfEmpty(Mono.defer(() -> this.second.route(request)));
331+
}
332+
333+
@Override
334+
public void accept(Visitor visitor) {
335+
this.first.accept(visitor);
336+
this.second.accept(visitor);
337+
}
338+
}
339+
340+
final static class DifferentComposedRouterFunction extends AbstractRouterFunction<ServerResponse> {
341+
342+
private final RouterFunction<?> first;
343+
344+
private final RouterFunction<?> second;
345+
346+
public DifferentComposedRouterFunction(RouterFunction<?> first, RouterFunction<?> second) {
347+
this.first = first;
348+
this.second = second;
349+
}
350+
351+
@Override
352+
public Mono<HandlerFunction<ServerResponse>> route(ServerRequest request) {
353+
return this.first.route(request)
354+
.map(RouterFunctions::cast)
355+
.switchIfEmpty(Mono.defer(() -> this.second.route(request).map(RouterFunctions::cast)));
356+
}
357+
358+
@Override
359+
public void accept(Visitor visitor) {
360+
this.first.accept(visitor);
361+
this.second.accept(visitor);
362+
}
363+
364+
}
365+
366+
final static class FilteredRouterFunction<T extends ServerResponse, S extends ServerResponse>
367+
implements RouterFunction<S> {
368+
369+
private final RouterFunction<T> routerFunction;
370+
371+
private final HandlerFilterFunction<T, S> filterFunction;
372+
373+
public FilteredRouterFunction(
374+
RouterFunction<T> routerFunction,
375+
HandlerFilterFunction<T, S> filterFunction) {
376+
this.routerFunction = routerFunction;
377+
this.filterFunction = filterFunction;
378+
}
379+
380+
@Override
381+
public Mono<HandlerFunction<S>> route(ServerRequest request) {
382+
return this.routerFunction.route(request).map(this.filterFunction::apply);
383+
}
384+
385+
@Override
386+
public void accept(Visitor visitor) {
387+
this.routerFunction.accept(visitor);
388+
}
389+
390+
}
391+
392+
private static final class DefaultRouterFunction<T extends ServerResponse>
393+
extends AbstractRouterFunction<T> {
264394

265395
private final RequestPredicate predicate;
266396

@@ -287,13 +417,14 @@ public Mono<HandlerFunction<T>> route(ServerRequest request) {
287417
}
288418

289419
@Override
290-
public String toString() {
291-
return String.format("%s -> %s", this.predicate, this.handlerFunction);
420+
public void accept(Visitor visitor) {
421+
visitor.route(this.predicate, this.handlerFunction);
292422
}
423+
293424
}
294425

295-
private static class DefaultNestedRouterFunction<T extends ServerResponse>
296-
implements RouterFunction<T> {
426+
private static final class DefaultNestedRouterFunction<T extends ServerResponse>
427+
extends AbstractRouterFunction<T> {
297428

298429
private final RequestPredicate predicate;
299430

@@ -322,12 +453,33 @@ public Mono<HandlerFunction<T>> route(ServerRequest serverRequest) {
322453
}
323454

324455
@Override
325-
public String toString() {
326-
return String.format("%s -> %s", this.predicate, this.routerFunction);
456+
public void accept(Visitor visitor) {
457+
visitor.startNested(this.predicate);
458+
this.routerFunction.accept(visitor);
459+
visitor.endNested(this.predicate);
327460
}
328461

329462
}
330463

464+
private static class ResourcesRouterFunction extends AbstractRouterFunction<ServerResponse> {
465+
466+
private final Function<ServerRequest, Mono<Resource>> lookupFunction;
467+
468+
public ResourcesRouterFunction(Function<ServerRequest, Mono<Resource>> lookupFunction) {
469+
this.lookupFunction = lookupFunction;
470+
}
471+
472+
@Override
473+
public Mono<HandlerFunction<ServerResponse>> route(ServerRequest request) {
474+
return this.lookupFunction.apply(request).map(ResourceHandlerFunction::new);
475+
}
476+
477+
@Override
478+
public void accept(Visitor visitor) {
479+
visitor.resources(this.lookupFunction);
480+
}
481+
}
482+
331483
private static class HandlerStrategiesResponseContext implements ServerResponse.Context {
332484

333485
private final HandlerStrategies strategies;
@@ -346,4 +498,5 @@ public List<ViewResolver> viewResolvers() {
346498
return this.strategies.viewResolvers();
347499
}
348500
}
501+
349502
}
Lines changed: 91 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,91 @@
1+
/*
2+
* Copyright 2002-2017 the original author or authors.
3+
*
4+
* Licensed under the Apache License, Version 2.0 (the "License");
5+
* you may not use this file except in compliance with the License.
6+
* You may obtain a copy of the License at
7+
*
8+
* http://www.apache.org/licenses/LICENSE-2.0
9+
*
10+
* Unless required by applicable law or agreed to in writing, software
11+
* distributed under the License is distributed on an "AS IS" BASIS,
12+
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13+
* See the License for the specific language governing permissions and
14+
* limitations under the License.
15+
*/
16+
17+
package org.springframework.web.reactive.function.server;
18+
19+
import java.util.function.Function;
20+
21+
import reactor.core.publisher.Mono;
22+
23+
import org.springframework.core.io.Resource;
24+
25+
/**
26+
* Implementation of {@link RouterFunctions.Visitor} that creates a
27+
* @author Arjen Poutsma
28+
* @since 5.0
29+
*/
30+
class ToStringVisitor implements RouterFunctions.Visitor {
31+
32+
private static final String NEW_LINE = System.getProperty("line.separator", "\\n");
33+
34+
private final StringBuilder builder = new StringBuilder();
35+
36+
private int indent = 0;
37+
38+
@Override
39+
public void startNested(RequestPredicate predicate) {
40+
indent();
41+
this.builder.append(predicate);
42+
this.builder.append(" => {");
43+
this.builder.append(NEW_LINE);
44+
this.indent++;
45+
}
46+
47+
@Override
48+
public void endNested(RequestPredicate predicate) {
49+
this.indent--;
50+
indent();
51+
this.builder.append('}');
52+
this.builder.append(NEW_LINE);
53+
}
54+
55+
@Override
56+
public void route(RequestPredicate predicate, HandlerFunction<?> handlerFunction) {
57+
indent();
58+
this.builder.append(predicate);
59+
this.builder.append(" -> ");
60+
this.builder.append(handlerFunction);
61+
this.builder.append(NEW_LINE);
62+
}
63+
64+
@Override
65+
public void resources(Function<ServerRequest, Mono<Resource>> lookupFunction) {
66+
indent();
67+
this.builder.append(lookupFunction);
68+
this.builder.append(NEW_LINE);
69+
}
70+
71+
@Override
72+
public void unknown(RouterFunction<?> routerFunction) {
73+
indent();
74+
this.builder.append(routerFunction);
75+
}
76+
77+
private void indent() {
78+
for (int i=0; i < this.indent; i++) {
79+
this.builder.append(' ');
80+
}
81+
}
82+
83+
@Override
84+
public String toString() {
85+
String result = this.builder.toString();
86+
if (result.endsWith(NEW_LINE)) {
87+
result = result.substring(0, result.length() - NEW_LINE.length());
88+
}
89+
return result;
90+
}
91+
}

spring-webflux/src/main/java/org/springframework/web/reactive/function/server/support/RouterFunctionMapping.java

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -102,8 +102,8 @@ protected void initRouterFunctions() {
102102

103103
List<RouterFunction<?>> routerFunctions = routerFunctions();
104104
if (!CollectionUtils.isEmpty(routerFunctions) && logger.isInfoEnabled()) {
105-
routerFunctions.forEach(routerFunction1 -> {
106-
logger.info("Mapped " + routerFunction1);
105+
routerFunctions.forEach(routerFunction -> {
106+
logger.info("Mapped " + routerFunction);
107107
});
108108
}
109109
this.routerFunction = routerFunctions.stream()

0 commit comments

Comments
 (0)