Browse Source

Improve attribute handling in RequestPredicates

This commit changes the way request attributes are handled in
RequestPredicates. Previously, the AND/OR/NOT predicates copied all
attributes in a map, and restored that when the delegate predicate(s)
failed.
Now, we only set the attributes when all delegates have succeeded.

Closes gh-30028
pull/31226/head
James Yuzawa 2 years ago committed by Arjen Poutsma
parent
commit
39786e4790
  1. 185
      spring-webflux/src/main/java/org/springframework/web/reactive/function/server/RequestPredicates.java
  2. 12
      spring-webflux/src/test/java/org/springframework/web/reactive/function/server/RequestPredicateAttributesTests.java
  3. 185
      spring-webmvc/src/main/java/org/springframework/web/servlet/function/RequestPredicates.java

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

@ -21,7 +21,6 @@ import java.net.URI;
import java.security.Principal; import java.security.Principal;
import java.util.Arrays; import java.util.Arrays;
import java.util.Collections; import java.util.Collections;
import java.util.HashMap;
import java.util.LinkedHashMap; import java.util.LinkedHashMap;
import java.util.LinkedHashSet; import java.util.LinkedHashSet;
import java.util.List; import java.util.List;
@ -296,11 +295,6 @@ public abstract class RequestPredicates {
} }
} }
private static void restoreAttributes(ServerRequest request, Map<String, Object> attributes) {
request.attributes().clear();
request.attributes().putAll(attributes);
}
private static Map<String, String> mergePathVariables(Map<String, String> oldVariables, private static Map<String, String> mergePathVariables(Map<String, String> oldVariables,
Map<String, String> newVariables) { Map<String, String> newVariables) {
@ -432,6 +426,81 @@ public abstract class RequestPredicates {
} }
/**
* A boolean result with a state-changing commit step to be conditionally
* applied by a caller.
*/
static abstract class Evaluation {
static final Evaluation TRUE = new NopEvaluation(true);
static final Evaluation FALSE = new NopEvaluation(false);
private final boolean value;
Evaluation(boolean value) {
this.value = value;
}
abstract void doCommit();
private static final class NopEvaluation extends Evaluation {
private NopEvaluation(boolean value) {
super(value);
}
@Override
void doCommit() {
// pass
}
}
}
/**
* Evaluates a {@link ServerRequest} and returns an {@link Evaluation}.
*/
private static abstract class Evaluator {
private static Evaluator of(RequestPredicate requestPredicate) {
if (requestPredicate instanceof EvaluatorRequestPredicate evaluatorRequestPredicate) {
return evaluatorRequestPredicate;
}
// Wrap the RequestPredicate with an Evaluator
return new RequestPredicateEvaluator(requestPredicate);
}
abstract Evaluation apply(ServerRequest request);
private static final class RequestPredicateEvaluator extends Evaluator {
private final RequestPredicate requestPredicate;
private RequestPredicateEvaluator(RequestPredicate requestPredicate) {
this.requestPredicate = requestPredicate;
}
@Override
Evaluation apply(ServerRequest request) {
return this.requestPredicate.test(request) ? Evaluation.TRUE : Evaluation.FALSE;
}
}
}
/**
* A {@link RequestPredicate} which may modify the request.
*/
static abstract class EvaluatorRequestPredicate extends Evaluator implements RequestPredicate {
@Override
public final boolean test(ServerRequest request) {
Evaluation result = apply(request);
boolean value = result.value;
if (value) {
result.doCommit();
}
return value;
}
}
private static class HttpMethodPredicate implements RequestPredicate { private static class HttpMethodPredicate implements RequestPredicate {
private final Set<HttpMethod> httpMethods; private final Set<HttpMethod> httpMethods;
@ -482,7 +551,7 @@ public abstract class RequestPredicates {
} }
private static class PathPatternPredicate implements RequestPredicate, ChangePathPatternParserVisitor.Target { private static class PathPatternPredicate extends EvaluatorRequestPredicate implements ChangePathPatternParserVisitor.Target {
private PathPattern pattern; private PathPattern pattern;
@ -492,29 +561,28 @@ public abstract class RequestPredicates {
} }
@Override @Override
public boolean test(ServerRequest request) { public Evaluation apply(ServerRequest request) {
PathContainer pathContainer = request.requestPath().pathWithinApplication(); PathContainer pathContainer = request.requestPath().pathWithinApplication();
PathPattern.PathMatchInfo info = this.pattern.matchAndExtract(pathContainer); PathPattern.PathMatchInfo info = this.pattern.matchAndExtract(pathContainer);
traceMatch("Pattern", this.pattern.getPatternString(), request.path(), info != null); traceMatch("Pattern", this.pattern.getPatternString(), request.path(), info != null);
if (info != null) { if (info == null) {
mergeAttributes(request, info.getUriVariables(), this.pattern); return Evaluation.FALSE;
return true;
}
else {
return false;
}
} }
return new Evaluation(true) {
private static void mergeAttributes(ServerRequest request, Map<String, String> variables, @Override
PathPattern pattern) { void doCommit() {
Map<String, String> pathVariables = mergePathVariables(request.pathVariables(), variables); Map<String, Object> attributes = request.attributes();
request.attributes().put(RouterFunctions.URI_TEMPLATE_VARIABLES_ATTRIBUTE, Map<String, String> pathVariables = mergePathVariables(request.pathVariables(),
info.getUriVariables());
attributes.put(RouterFunctions.URI_TEMPLATE_VARIABLES_ATTRIBUTE,
Collections.unmodifiableMap(pathVariables)); Collections.unmodifiableMap(pathVariables));
pattern = mergePatterns( PathPattern newPattern = mergePatterns(
(PathPattern) request.attributes().get(RouterFunctions.MATCHING_PATTERN_ATTRIBUTE), (PathPattern) attributes.get(RouterFunctions.MATCHING_PATTERN_ATTRIBUTE),
pattern); PathPatternPredicate.this.pattern);
request.attributes().put(RouterFunctions.MATCHING_PATTERN_ATTRIBUTE, pattern); attributes.put(RouterFunctions.MATCHING_PATTERN_ATTRIBUTE, newPattern);
}
};
} }
@Override @Override
@ -756,28 +824,40 @@ public abstract class RequestPredicates {
* {@link RequestPredicate} for where both {@code left} and {@code right} predicates * {@link RequestPredicate} for where both {@code left} and {@code right} predicates
* must match. * must match.
*/ */
static class AndRequestPredicate implements RequestPredicate, ChangePathPatternParserVisitor.Target { static class AndRequestPredicate extends EvaluatorRequestPredicate implements ChangePathPatternParserVisitor.Target {
private final RequestPredicate left; private final RequestPredicate left;
private final Evaluator leftEvaluator;
private final RequestPredicate right; private final RequestPredicate right;
private final Evaluator rightEvaluator;
public AndRequestPredicate(RequestPredicate left, RequestPredicate right) { public AndRequestPredicate(RequestPredicate left, RequestPredicate right) {
Assert.notNull(left, "Left RequestPredicate must not be null"); Assert.notNull(left, "Left RequestPredicate must not be null");
Assert.notNull(right, "Right RequestPredicate must not be null"); Assert.notNull(right, "Right RequestPredicate must not be null");
this.left = left; this.left = left;
this.leftEvaluator = Evaluator.of(left);
this.right = right; this.right = right;
this.rightEvaluator = Evaluator.of(right);
} }
@Override @Override
public boolean test(ServerRequest request) { public Evaluation apply(ServerRequest request) {
Map<String, Object> oldAttributes = new HashMap<>(request.attributes()); Evaluation leftResult = this.leftEvaluator.apply(request);
if (!leftResult.value) {
if (this.left.test(request) && this.right.test(request)) { return leftResult;
return true;
} }
restoreAttributes(request, oldAttributes); Evaluation rightResult = this.rightEvaluator.apply(request);
return false; if (!rightResult.value) {
return rightResult;
}
return new Evaluation(true) {
@Override
void doCommit() {
leftResult.doCommit();
rightResult.doCommit();
}
};
} }
@Override @Override
@ -814,23 +894,26 @@ public abstract class RequestPredicates {
/** /**
* {@link RequestPredicate} that negates a delegate predicate. * {@link RequestPredicate} that negates a delegate predicate.
*/ */
static class NegateRequestPredicate implements RequestPredicate, ChangePathPatternParserVisitor.Target { static class NegateRequestPredicate extends EvaluatorRequestPredicate implements ChangePathPatternParserVisitor.Target {
private final RequestPredicate delegate; private final RequestPredicate delegate;
private final Evaluator delegateEvaluator;
public NegateRequestPredicate(RequestPredicate delegate) { public NegateRequestPredicate(RequestPredicate delegate) {
Assert.notNull(delegate, "Delegate must not be null"); Assert.notNull(delegate, "Delegate must not be null");
this.delegate = delegate; this.delegate = delegate;
this.delegateEvaluator = Evaluator.of(delegate);
} }
@Override @Override
public boolean test(ServerRequest request) { public Evaluation apply(ServerRequest request) {
Map<String, Object> oldAttributes = new HashMap<>(request.attributes()); Evaluation result = this.delegateEvaluator.apply(request);
boolean result = !this.delegate.test(request); return new Evaluation(!result.value) {
if (!result) { @Override
restoreAttributes(request, oldAttributes); void doCommit() {
result.doCommit();
} }
return result; };
} }
@Override @Override
@ -858,34 +941,30 @@ public abstract class RequestPredicates {
* {@link RequestPredicate} where either {@code left} or {@code right} predicates * {@link RequestPredicate} where either {@code left} or {@code right} predicates
* may match. * may match.
*/ */
static class OrRequestPredicate implements RequestPredicate, ChangePathPatternParserVisitor.Target { static class OrRequestPredicate extends EvaluatorRequestPredicate implements ChangePathPatternParserVisitor.Target {
private final RequestPredicate left; private final RequestPredicate left;
private final Evaluator leftEvaluator;
private final RequestPredicate right; private final RequestPredicate right;
private final Evaluator rightEvaluator;
public OrRequestPredicate(RequestPredicate left, RequestPredicate right) { public OrRequestPredicate(RequestPredicate left, RequestPredicate right) {
Assert.notNull(left, "Left RequestPredicate must not be null"); Assert.notNull(left, "Left RequestPredicate must not be null");
Assert.notNull(right, "Right RequestPredicate must not be null"); Assert.notNull(right, "Right RequestPredicate must not be null");
this.left = left; this.left = left;
this.leftEvaluator = Evaluator.of(left);
this.right = right; this.right = right;
this.rightEvaluator = Evaluator.of(right);
} }
@Override @Override
public boolean test(ServerRequest request) { public Evaluation apply(ServerRequest request) {
Map<String, Object> oldAttributes = new HashMap<>(request.attributes()); Evaluation leftResult = this.leftEvaluator.apply(request);
if (leftResult.value) {
if (this.left.test(request)) { return leftResult;
return true;
}
else {
restoreAttributes(request, oldAttributes);
if (this.right.test(request)) {
return true;
}
} }
restoreAttributes(request, oldAttributes); return this.rightEvaluator.apply(request);
return false;
} }
@Override @Override

12
spring-webflux/src/test/java/org/springframework/web/reactive/function/server/RequestPredicateAttributesTests.java

@ -23,6 +23,8 @@ import org.junit.jupiter.api.Test;
import org.springframework.core.codec.StringDecoder; import org.springframework.core.codec.StringDecoder;
import org.springframework.http.codec.DecoderHttpMessageReader; import org.springframework.http.codec.DecoderHttpMessageReader;
import org.springframework.web.reactive.function.server.RequestPredicates.Evaluation;
import org.springframework.web.reactive.function.server.RequestPredicates.EvaluatorRequestPredicate;
import org.springframework.web.testfixture.http.server.reactive.MockServerHttpRequest; import org.springframework.web.testfixture.http.server.reactive.MockServerHttpRequest;
import org.springframework.web.testfixture.server.MockServerWebExchange; import org.springframework.web.testfixture.server.MockServerWebExchange;
@ -182,7 +184,7 @@ public class RequestPredicateAttributesTests {
} }
private static class AddAttributePredicate implements RequestPredicate { private static class AddAttributePredicate extends EvaluatorRequestPredicate {
private boolean result; private boolean result;
@ -197,9 +199,13 @@ public class RequestPredicateAttributesTests {
} }
@Override @Override
public boolean test(ServerRequest request) { public Evaluation apply(ServerRequest request) {
return new Evaluation(result) {
@Override
void doCommit() {
request.attributes().put(key, value); request.attributes().put(key, value);
return this.result; }
};
} }
} }

185
spring-webmvc/src/main/java/org/springframework/web/servlet/function/RequestPredicates.java

@ -23,7 +23,6 @@ import java.security.Principal;
import java.time.Instant; import java.time.Instant;
import java.util.Arrays; import java.util.Arrays;
import java.util.Collections; import java.util.Collections;
import java.util.HashMap;
import java.util.LinkedHashMap; import java.util.LinkedHashMap;
import java.util.LinkedHashSet; import java.util.LinkedHashSet;
import java.util.List; import java.util.List;
@ -295,11 +294,6 @@ public abstract class RequestPredicates {
} }
} }
private static void restoreAttributes(ServerRequest request, Map<String, Object> attributes) {
request.attributes().clear();
request.attributes().putAll(attributes);
}
private static Map<String, String> mergePathVariables(Map<String, String> oldVariables, private static Map<String, String> mergePathVariables(Map<String, String> oldVariables,
Map<String, String> newVariables) { Map<String, String> newVariables) {
@ -431,6 +425,81 @@ public abstract class RequestPredicates {
} }
/**
* A boolean result with a state-changing commit step to be conditionally
* applied by a caller.
*/
static abstract class Evaluation {
static final Evaluation TRUE = new NopEvaluation(true);
static final Evaluation FALSE = new NopEvaluation(false);
private final boolean value;
Evaluation(boolean value) {
this.value = value;
}
abstract void doCommit();
private static final class NopEvaluation extends Evaluation {
private NopEvaluation(boolean value) {
super(value);
}
@Override
void doCommit() {
// pass
}
}
}
/**
* Evaluates a {@link ServerRequest} and returns an {@link Evaluation}.
*/
private static abstract class Evaluator {
private static Evaluator of(RequestPredicate requestPredicate) {
if (requestPredicate instanceof EvaluatorRequestPredicate evaluatorRequestPredicate) {
return evaluatorRequestPredicate;
}
// Wrap the RequestPredicate with an Evaluator
return new RequestPredicateEvaluator(requestPredicate);
}
abstract Evaluation apply(ServerRequest request);
private static final class RequestPredicateEvaluator extends Evaluator {
private final RequestPredicate requestPredicate;
private RequestPredicateEvaluator(RequestPredicate requestPredicate) {
this.requestPredicate = requestPredicate;
}
@Override
Evaluation apply(ServerRequest request) {
return this.requestPredicate.test(request) ? Evaluation.TRUE : Evaluation.FALSE;
}
}
}
/**
* A {@link RequestPredicate} which may modify the request.
*/
static abstract class EvaluatorRequestPredicate extends Evaluator implements RequestPredicate {
@Override
public final boolean test(ServerRequest request) {
Evaluation result = apply(request);
boolean value = result.value;
if (value) {
result.doCommit();
}
return value;
}
}
private static class HttpMethodPredicate implements RequestPredicate { private static class HttpMethodPredicate implements RequestPredicate {
private final Set<HttpMethod> httpMethods; private final Set<HttpMethod> httpMethods;
@ -481,7 +550,7 @@ public abstract class RequestPredicates {
} }
private static class PathPatternPredicate implements RequestPredicate, ChangePathPatternParserVisitor.Target { private static class PathPatternPredicate extends EvaluatorRequestPredicate implements ChangePathPatternParserVisitor.Target {
private PathPattern pattern; private PathPattern pattern;
@ -491,29 +560,28 @@ public abstract class RequestPredicates {
} }
@Override @Override
public boolean test(ServerRequest request) { public Evaluation apply(ServerRequest request) {
PathContainer pathContainer = request.requestPath().pathWithinApplication(); PathContainer pathContainer = request.requestPath().pathWithinApplication();
PathPattern.PathMatchInfo info = this.pattern.matchAndExtract(pathContainer); PathPattern.PathMatchInfo info = this.pattern.matchAndExtract(pathContainer);
traceMatch("Pattern", this.pattern.getPatternString(), request.path(), info != null); traceMatch("Pattern", this.pattern.getPatternString(), request.path(), info != null);
if (info != null) { if (info == null) {
mergeAttributes(request, info.getUriVariables(), this.pattern); return Evaluation.FALSE;
return true;
}
else {
return false;
}
} }
return new Evaluation(true) {
private static void mergeAttributes(ServerRequest request, Map<String, String> variables, @Override
PathPattern pattern) { void doCommit() {
Map<String, String> pathVariables = mergePathVariables(request.pathVariables(), variables); Map<String, Object> attributes = request.attributes();
request.attributes().put(RouterFunctions.URI_TEMPLATE_VARIABLES_ATTRIBUTE, Map<String, String> pathVariables = mergePathVariables(request.pathVariables(),
info.getUriVariables());
attributes.put(RouterFunctions.URI_TEMPLATE_VARIABLES_ATTRIBUTE,
Collections.unmodifiableMap(pathVariables)); Collections.unmodifiableMap(pathVariables));
pattern = mergePatterns( PathPattern newPattern = mergePatterns(
(PathPattern) request.attributes().get(RouterFunctions.MATCHING_PATTERN_ATTRIBUTE), (PathPattern) attributes.get(RouterFunctions.MATCHING_PATTERN_ATTRIBUTE),
pattern); PathPatternPredicate.this.pattern);
request.attributes().put(RouterFunctions.MATCHING_PATTERN_ATTRIBUTE, pattern); attributes.put(RouterFunctions.MATCHING_PATTERN_ATTRIBUTE, newPattern);
}
};
} }
@Override @Override
@ -755,28 +823,40 @@ public abstract class RequestPredicates {
* {@link RequestPredicate} for where both {@code left} and {@code right} predicates * {@link RequestPredicate} for where both {@code left} and {@code right} predicates
* must match. * must match.
*/ */
static class AndRequestPredicate implements RequestPredicate, ChangePathPatternParserVisitor.Target { static class AndRequestPredicate extends EvaluatorRequestPredicate implements ChangePathPatternParserVisitor.Target {
private final RequestPredicate left; private final RequestPredicate left;
private final Evaluator leftEvaluator;
private final RequestPredicate right; private final RequestPredicate right;
private final Evaluator rightEvaluator;
public AndRequestPredicate(RequestPredicate left, RequestPredicate right) { public AndRequestPredicate(RequestPredicate left, RequestPredicate right) {
Assert.notNull(left, "Left RequestPredicate must not be null"); Assert.notNull(left, "Left RequestPredicate must not be null");
Assert.notNull(right, "Right RequestPredicate must not be null"); Assert.notNull(right, "Right RequestPredicate must not be null");
this.left = left; this.left = left;
this.leftEvaluator = Evaluator.of(left);
this.right = right; this.right = right;
this.rightEvaluator = Evaluator.of(right);
} }
@Override @Override
public boolean test(ServerRequest request) { public Evaluation apply(ServerRequest request) {
Map<String, Object> oldAttributes = new HashMap<>(request.attributes()); Evaluation leftResult = this.leftEvaluator.apply(request);
if (!leftResult.value) {
if (this.left.test(request) && this.right.test(request)) { return leftResult;
return true;
} }
restoreAttributes(request, oldAttributes); Evaluation rightResult = this.rightEvaluator.apply(request);
return false; if (!rightResult.value) {
return rightResult;
}
return new Evaluation(true) {
@Override
void doCommit() {
leftResult.doCommit();
rightResult.doCommit();
}
};
} }
@Override @Override
@ -813,23 +893,26 @@ public abstract class RequestPredicates {
/** /**
* {@link RequestPredicate} that negates a delegate predicate. * {@link RequestPredicate} that negates a delegate predicate.
*/ */
static class NegateRequestPredicate implements RequestPredicate, ChangePathPatternParserVisitor.Target { static class NegateRequestPredicate extends EvaluatorRequestPredicate implements ChangePathPatternParserVisitor.Target {
private final RequestPredicate delegate; private final RequestPredicate delegate;
private final Evaluator delegateEvaluator;
public NegateRequestPredicate(RequestPredicate delegate) { public NegateRequestPredicate(RequestPredicate delegate) {
Assert.notNull(delegate, "Delegate must not be null"); Assert.notNull(delegate, "Delegate must not be null");
this.delegate = delegate; this.delegate = delegate;
this.delegateEvaluator = Evaluator.of(delegate);
} }
@Override @Override
public boolean test(ServerRequest request) { public Evaluation apply(ServerRequest request) {
Map<String, Object> oldAttributes = new HashMap<>(request.attributes()); Evaluation result = this.delegateEvaluator.apply(request);
boolean result = !this.delegate.test(request); return new Evaluation(!result.value) {
if (!result) { @Override
restoreAttributes(request, oldAttributes); void doCommit() {
result.doCommit();
} }
return result; };
} }
@Override @Override
@ -857,34 +940,30 @@ public abstract class RequestPredicates {
* {@link RequestPredicate} where either {@code left} or {@code right} predicates * {@link RequestPredicate} where either {@code left} or {@code right} predicates
* may match. * may match.
*/ */
static class OrRequestPredicate implements RequestPredicate, ChangePathPatternParserVisitor.Target { static class OrRequestPredicate extends EvaluatorRequestPredicate implements ChangePathPatternParserVisitor.Target {
private final RequestPredicate left; private final RequestPredicate left;
private final Evaluator leftEvaluator;
private final RequestPredicate right; private final RequestPredicate right;
private final Evaluator rightEvaluator;
public OrRequestPredicate(RequestPredicate left, RequestPredicate right) { public OrRequestPredicate(RequestPredicate left, RequestPredicate right) {
Assert.notNull(left, "Left RequestPredicate must not be null"); Assert.notNull(left, "Left RequestPredicate must not be null");
Assert.notNull(right, "Right RequestPredicate must not be null"); Assert.notNull(right, "Right RequestPredicate must not be null");
this.left = left; this.left = left;
this.leftEvaluator = Evaluator.of(left);
this.right = right; this.right = right;
this.rightEvaluator = Evaluator.of(right);
} }
@Override @Override
public boolean test(ServerRequest request) { public Evaluation apply(ServerRequest request) {
Map<String, Object> oldAttributes = new HashMap<>(request.attributes()); Evaluation leftResult = this.leftEvaluator.apply(request);
if (leftResult.value) {
if (this.left.test(request)) { return leftResult;
return true;
}
else {
restoreAttributes(request, oldAttributes);
if (this.right.test(request)) {
return true;
}
} }
restoreAttributes(request, oldAttributes); return this.rightEvaluator.apply(request);
return false;
} }
@Override @Override

Loading…
Cancel
Save