Adds Visitor pattern to visit each node of Predicate. (#2423)

This allows users to inspect the configuration of predicates and perform work on them.
This commit is contained in:
Spencer Gibb
2021-10-29 15:00:37 -04:00
committed by GitHub
parent 5620be9fc5
commit 665d7df1e6
16 changed files with 239 additions and 2 deletions

View File

@@ -23,13 +23,15 @@ import org.reactivestreams.Publisher;
import reactor.core.publisher.Mono;
import org.springframework.cloud.gateway.handler.predicate.GatewayPredicate;
import org.springframework.cloud.gateway.support.HasConfig;
import org.springframework.cloud.gateway.support.Visitor;
import org.springframework.util.Assert;
import org.springframework.web.server.ServerWebExchange;
/**
* @author Ben Hale
*/
public interface AsyncPredicate<T> extends Function<T, Publisher<Boolean>> {
public interface AsyncPredicate<T> extends Function<T, Publisher<Boolean>>, HasConfig {
default AsyncPredicate<T> and(AsyncPredicate<? super T> other) {
return new AndAsyncPredicate<>(this, other);
@@ -47,6 +49,10 @@ public interface AsyncPredicate<T> extends Function<T, Publisher<Boolean>> {
return new OrAsyncPredicate<>(this, other);
}
default void accept(Visitor visitor) {
visitor.visit(this);
}
static AsyncPredicate<ServerWebExchange> from(Predicate<? super ServerWebExchange> predicate) {
return new DefaultAsyncPredicate<>(GatewayPredicate.wrapIfNeeded(predicate));
}
@@ -69,6 +75,14 @@ public interface AsyncPredicate<T> extends Function<T, Publisher<Boolean>> {
return this.delegate.toString();
}
@Override
public void accept(Visitor visitor) {
if (delegate instanceof GatewayPredicate) {
GatewayPredicate gatewayPredicate = (GatewayPredicate) delegate;
gatewayPredicate.accept(visitor);
}
}
}
class NegateAsyncPredicate<T> implements AsyncPredicate<T> {
@@ -110,6 +124,12 @@ public interface AsyncPredicate<T> extends Function<T, Publisher<Boolean>> {
return Mono.from(left.apply(t)).flatMap(result -> !result ? Mono.just(false) : Mono.from(right.apply(t)));
}
@Override
public void accept(Visitor visitor) {
left.accept(visitor);
right.accept(visitor);
}
@Override
public String toString() {
return String.format("(%s && %s)", this.left, this.right);
@@ -135,6 +155,12 @@ public interface AsyncPredicate<T> extends Function<T, Publisher<Boolean>> {
return Mono.from(left.apply(t)).flatMap(result -> result ? Mono.just(true) : Mono.from(right.apply(t)));
}
@Override
public void accept(Visitor visitor) {
left.accept(visitor);
right.accept(visitor);
}
@Override
public String toString() {
return String.format("(%s || %s)", this.left, this.right);

View File

@@ -53,6 +53,11 @@ public class AfterRoutePredicateFactory extends AbstractRoutePredicateFactory<Af
return now.isAfter(config.getDatetime());
}
@Override
public Object getConfig() {
return config;
}
@Override
public String toString() {
return String.format("After: %s", config.getDatetime());

View File

@@ -51,6 +51,11 @@ public class BeforeRoutePredicateFactory extends AbstractRoutePredicateFactory<B
return now.isBefore(config.getDatetime());
}
@Override
public Object getConfig() {
return config;
}
@Override
public String toString() {
return String.format("Before: %s", config.getDatetime());

View File

@@ -63,6 +63,11 @@ public class BetweenRoutePredicateFactory extends AbstractRoutePredicateFactory<
return now.isAfter(config.getDatetime1()) && now.isBefore(config.getDatetime2());
}
@Override
public Object getConfig() {
return config;
}
@Override
public String toString() {
return String.format("Between: %s and %s", config.getDatetime1(), config.getDatetime2());

View File

@@ -67,6 +67,11 @@ public class CookieRoutePredicateFactory extends AbstractRoutePredicateFactory<C
return false;
}
@Override
public Object getConfig() {
return config;
}
@Override
public String toString() {
return String.format("Cookie: name=%s regexp=%s", config.name, config.regexp);

View File

@@ -18,10 +18,12 @@ package org.springframework.cloud.gateway.handler.predicate;
import java.util.function.Predicate;
import org.springframework.cloud.gateway.support.HasConfig;
import org.springframework.cloud.gateway.support.Visitor;
import org.springframework.util.Assert;
import org.springframework.web.server.ServerWebExchange;
public interface GatewayPredicate extends Predicate<ServerWebExchange> {
public interface GatewayPredicate extends Predicate<ServerWebExchange>, HasConfig {
@Override
default Predicate<ServerWebExchange> and(Predicate<? super ServerWebExchange> other) {
@@ -38,6 +40,10 @@ public interface GatewayPredicate extends Predicate<ServerWebExchange> {
return new OrGatewayPredicate(this, wrapIfNeeded(other));
}
default void accept(Visitor visitor) {
visitor.visit(this);
}
static GatewayPredicate wrapIfNeeded(Predicate<? super ServerWebExchange> other) {
GatewayPredicate right;
@@ -64,6 +70,14 @@ public interface GatewayPredicate extends Predicate<ServerWebExchange> {
return this.delegate.test(exchange);
}
@Override
public void accept(Visitor visitor) {
if (delegate instanceof GatewayPredicate) {
GatewayPredicate gatewayPredicate = (GatewayPredicate) delegate;
gatewayPredicate.accept(visitor);
}
}
@Override
public String toString() {
return this.delegate.getClass().getSimpleName();
@@ -85,6 +99,11 @@ public interface GatewayPredicate extends Predicate<ServerWebExchange> {
return !this.predicate.test(t);
}
@Override
public void accept(Visitor visitor) {
predicate.accept(visitor);
}
@Override
public String toString() {
return String.format("!%s", this.predicate);
@@ -110,6 +129,12 @@ public interface GatewayPredicate extends Predicate<ServerWebExchange> {
return (this.left.test(t) && this.right.test(t));
}
@Override
public void accept(Visitor visitor) {
left.accept(visitor);
right.accept(visitor);
}
@Override
public String toString() {
return String.format("(%s && %s)", this.left, this.right);
@@ -135,6 +160,12 @@ public interface GatewayPredicate extends Predicate<ServerWebExchange> {
return (this.left.test(t) || this.right.test(t));
}
@Override
public void accept(Visitor visitor) {
left.accept(visitor);
right.accept(visitor);
}
@Override
public String toString() {
return String.format("(%s || %s)", this.left, this.right);

View File

@@ -79,6 +79,11 @@ public class HeaderRoutePredicateFactory extends AbstractRoutePredicateFactory<H
return true;
}
@Override
public Object getConfig() {
return config;
}
@Override
public String toString() {
return String.format("Header: %s regexp=%s", config.header, config.regexp);

View File

@@ -78,6 +78,11 @@ public class HostRoutePredicateFactory extends AbstractRoutePredicateFactory<Hos
return false;
}
@Override
public Object getConfig() {
return config;
}
@Override
public String toString() {
return String.format("Hosts: %s", config.getPatterns());

View File

@@ -109,6 +109,11 @@ public class PathRoutePredicateFactory extends AbstractRoutePredicateFactory<Pat
}
}
@Override
public Object getConfig() {
return config;
}
@Override
public String toString() {
return String.format("Paths: %s, match trailing slash: %b", config.getPatterns(),

View File

@@ -72,6 +72,11 @@ public class QueryRoutePredicateFactory extends AbstractRoutePredicateFactory<Qu
return false;
}
@Override
public Object getConfig() {
return config;
}
@Override
public String toString() {
return String.format("Query: param=%s regexp=%s", config.getParam(), config.getRegexp());

View File

@@ -97,6 +97,11 @@ public class ReadBodyRoutePredicateFactory extends AbstractRoutePredicateFactory
}
}
@Override
public Object getConfig() {
return config;
}
@Override
public String toString() {
return String.format("ReadBody: %s", config.getInClass());

View File

@@ -94,6 +94,11 @@ public class RemoteAddrRoutePredicateFactory
return false;
}
@Override
public Object getConfig() {
return config;
}
@Override
public String toString() {
return String.format("RemoteAddrs: %s", config.getSources());

View File

@@ -110,6 +110,11 @@ public class WeightRoutePredicateFactory extends AbstractRoutePredicateFactory<W
return false;
}
@Override
public Object getConfig() {
return config;
}
@Override
public String toString() {
return String.format("Weight: %s %s", config.getGroup(), config.getWeight());

View File

@@ -0,0 +1,25 @@
/*
* Copyright 2013-2020 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
*
* https://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.support;
public interface HasConfig {
default Object getConfig() {
return null;
}
}

View File

@@ -0,0 +1,30 @@
/*
* Copyright 2013-2021 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
*
* https://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.support;
/**
* Simple visitor interface that allows users to inspect a graph of objects.
*
* @author Spencer Gibb
* @since 3.1.0
*/
@FunctionalInterface
public interface Visitor {
void visit(HasConfig hasConfig);
}

View File

@@ -0,0 +1,70 @@
/*
* Copyright 2013-2020 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
*
* https://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.predicate;
import java.util.ArrayList;
import java.util.function.Predicate;
import org.junit.Test;
import org.springframework.cloud.gateway.handler.AsyncPredicate;
import org.springframework.cloud.gateway.route.Route;
import org.springframework.web.server.ServerWebExchange;
import static org.assertj.core.api.Assertions.assertThat;
/**
* @author Spencer Gibb
*/
public class GatewayPredicateVisitorTests {
@Test
public void asyncPredicateVisitVisitsEachNode() {
PathRoutePredicateFactory pathRoutePredicateFactory = new PathRoutePredicateFactory();
HostRoutePredicateFactory hostRoutePredicateFactory = new HostRoutePredicateFactory();
ReadBodyRoutePredicateFactory readBodyRoutePredicateFactory1 = new ReadBodyRoutePredicateFactory();
ReadBodyRoutePredicateFactory readBodyRoutePredicateFactory2 = new ReadBodyRoutePredicateFactory();
AsyncPredicate<ServerWebExchange> predicate = AsyncPredicate.from(pathRoutePredicateFactory.apply(pathRoutePredicateFactory.newConfig()))
.and(AsyncPredicate.from(hostRoutePredicateFactory.apply(hostRoutePredicateFactory.newConfig())))
.and(readBodyRoutePredicateFactory1.applyAsync(readBodyRoutePredicateFactory1.newConfig()))
.and(readBodyRoutePredicateFactory2.applyAsync(readBodyRoutePredicateFactory2.newConfig()));
Route route = Route.async().id("git").uri("http://myuri").asyncPredicate(predicate).build();
ArrayList<Object> configs = new ArrayList<>();
route.getPredicate().accept(p -> configs.add(p.getConfig()));
assertThat(configs).hasSize(4).hasExactlyElementsOfTypes(PathRoutePredicateFactory.Config.class,
HostRoutePredicateFactory.Config.class, ReadBodyRoutePredicateFactory.Config.class,
ReadBodyRoutePredicateFactory.Config.class);
}
@Test
public void predicateVisitVisitsEachNode() {
PathRoutePredicateFactory pathRoutePredicateFactory = new PathRoutePredicateFactory();
HostRoutePredicateFactory hostRoutePredicateFactory = new HostRoutePredicateFactory();
Predicate<ServerWebExchange> predicate = pathRoutePredicateFactory.apply(pathRoutePredicateFactory.newConfig())
.and(hostRoutePredicateFactory.apply(hostRoutePredicateFactory.newConfig()));
Route route = Route.async().id("git").uri("http://myuri").predicate(predicate).build();
ArrayList<Object> configs = new ArrayList<>();
route.getPredicate().accept(p -> configs.add(p.getConfig()));
assertThat(configs).hasSize(2).hasExactlyElementsOfTypes(PathRoutePredicateFactory.Config.class,
HostRoutePredicateFactory.Config.class);
}
}