001/*
002 * Copyright 2002-2020 the original author or authors.
003 *
004 * Licensed under the Apache License, Version 2.0 (the "License");
005 * you may not use this file except in compliance with the License.
006 * You may obtain a copy of the License at
007 *
008 *      https://www.apache.org/licenses/LICENSE-2.0
009 *
010 * Unless required by applicable law or agreed to in writing, software
011 * distributed under the License is distributed on an "AS IS" BASIS,
012 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
013 * See the License for the specific language governing permissions and
014 * limitations under the License.
015 */
016
017package org.springframework.web.servlet.function;
018
019import java.io.IOException;
020import java.net.InetSocketAddress;
021import java.net.URI;
022import java.security.Principal;
023import java.time.Instant;
024import java.util.ArrayList;
025import java.util.Arrays;
026import java.util.Collections;
027import java.util.EnumSet;
028import java.util.HashMap;
029import java.util.HashSet;
030import java.util.LinkedHashMap;
031import java.util.List;
032import java.util.Map;
033import java.util.Optional;
034import java.util.Set;
035import java.util.concurrent.ConcurrentHashMap;
036import java.util.function.Function;
037import java.util.function.Predicate;
038
039import javax.servlet.ServletException;
040import javax.servlet.http.Cookie;
041import javax.servlet.http.HttpServletRequest;
042import javax.servlet.http.HttpSession;
043
044import org.apache.commons.logging.Log;
045import org.apache.commons.logging.LogFactory;
046
047import org.springframework.core.ParameterizedTypeReference;
048import org.springframework.http.HttpHeaders;
049import org.springframework.http.HttpMethod;
050import org.springframework.http.MediaType;
051import org.springframework.http.converter.HttpMessageConverter;
052import org.springframework.http.server.PathContainer;
053import org.springframework.lang.NonNull;
054import org.springframework.lang.Nullable;
055import org.springframework.util.Assert;
056import org.springframework.util.MultiValueMap;
057import org.springframework.web.cors.CorsUtils;
058import org.springframework.web.util.UriBuilder;
059import org.springframework.web.util.UriUtils;
060import org.springframework.web.util.pattern.PathPattern;
061import org.springframework.web.util.pattern.PathPatternParser;
062
063/**
064 * Implementations of {@link RequestPredicate} that implement various useful
065 * request matching operations, such as matching based on path, HTTP method, etc.
066 *
067 * @author Arjen Poutsma
068 * @since 5.2
069 */
070public abstract class RequestPredicates {
071
072        private static final Log logger = LogFactory.getLog(RequestPredicates.class);
073
074        /**
075         * Return a {@code RequestPredicate} that always matches.
076         * @return a predicate that always matches
077         */
078        public static RequestPredicate all() {
079                return request -> true;
080        }
081
082        /**
083         * Return a {@code RequestPredicate} that matches if the request's
084         * HTTP method is equal to the given method.
085         * @param httpMethod the HTTP method to match against
086         * @return a predicate that tests against the given HTTP method
087         */
088        public static RequestPredicate method(HttpMethod httpMethod) {
089                return new HttpMethodPredicate(httpMethod);
090        }
091
092        /**
093         * Return a {@code RequestPredicate} that matches if the request's
094         * HTTP method is equal to one the of the given methods.
095         * @param httpMethods the HTTP methods to match against
096         * @return a predicate that tests against the given HTTP methods
097         */
098        public static RequestPredicate methods(HttpMethod... httpMethods) {
099                return new HttpMethodPredicate(httpMethods);
100        }
101
102        /**
103         * Return a {@code RequestPredicate} that tests the request path
104         * against the given path pattern.
105         * @param pattern the pattern to match to
106         * @return a predicate that tests against the given path pattern
107         */
108        public static RequestPredicate path(String pattern) {
109                Assert.notNull(pattern, "'pattern' must not be null");
110                if (!pattern.isEmpty() && !pattern.startsWith("/")) {
111                        pattern = "/" + pattern;
112                }
113                return pathPredicates(PathPatternParser.defaultInstance).apply(pattern);
114        }
115
116        /**
117         * Return a function that creates new path-matching {@code RequestPredicates}
118         * from pattern Strings using the given {@link PathPatternParser}.
119         * <p>This method can be used to specify a non-default, customized
120         * {@code PathPatternParser} when resolving path patterns.
121         * @param patternParser the parser used to parse patterns given to the returned function
122         * @return a function that resolves a pattern String into a path-matching
123         * {@code RequestPredicates} instance
124         */
125        public static Function<String, RequestPredicate> pathPredicates(PathPatternParser patternParser) {
126                Assert.notNull(patternParser, "PathPatternParser must not be null");
127                return pattern -> new PathPatternPredicate(patternParser.parse(pattern));
128        }
129
130        /**
131         * Return a {@code RequestPredicate} that tests the request's headers
132         * against the given headers predicate.
133         * @param headersPredicate a predicate that tests against the request headers
134         * @return a predicate that tests against the given header predicate
135         */
136        public static RequestPredicate headers(Predicate<ServerRequest.Headers> headersPredicate) {
137                return new HeadersPredicate(headersPredicate);
138        }
139
140        /**
141         * Return a {@code RequestPredicate} that tests if the request's
142         * {@linkplain ServerRequest.Headers#contentType() content type} is
143         * {@linkplain MediaType#includes(MediaType) included} by any of the given media types.
144         * @param mediaTypes the media types to match the request's content type against
145         * @return a predicate that tests the request's content type against the given media types
146         */
147        public static RequestPredicate contentType(MediaType... mediaTypes) {
148                Assert.notEmpty(mediaTypes, "'mediaTypes' must not be empty");
149                return new ContentTypePredicate(mediaTypes);
150        }
151
152        /**
153         * Return a {@code RequestPredicate} that tests if the request's
154         * {@linkplain ServerRequest.Headers#accept() accept} header is
155         * {@linkplain MediaType#isCompatibleWith(MediaType) compatible} with any of the given media types.
156         * @param mediaTypes the media types to match the request's accept header against
157         * @return a predicate that tests the request's accept header against the given media types
158         */
159        public static RequestPredicate accept(MediaType... mediaTypes) {
160                Assert.notEmpty(mediaTypes, "'mediaTypes' must not be empty");
161                return new AcceptPredicate(mediaTypes);
162        }
163
164        /**
165         * Return a {@code RequestPredicate} that matches if request's HTTP method is {@code GET}
166         * and the given {@code pattern} matches against the request path.
167         * @param pattern the path pattern to match against
168         * @return a predicate that matches if the request method is GET and if the given pattern
169         * matches against the request path
170         */
171        public static RequestPredicate GET(String pattern) {
172                return method(HttpMethod.GET).and(path(pattern));
173        }
174
175        /**
176         * Return a {@code RequestPredicate} that matches if request's HTTP method is {@code HEAD}
177         * and the given {@code pattern} matches against the request path.
178         * @param pattern the path pattern to match against
179         * @return a predicate that matches if the request method is HEAD and if the given pattern
180         * matches against the request path
181         */
182        public static RequestPredicate HEAD(String pattern) {
183                return method(HttpMethod.HEAD).and(path(pattern));
184        }
185
186        /**
187         * Return a {@code RequestPredicate} that matches if request's HTTP method is {@code POST}
188         * and the given {@code pattern} matches against the request path.
189         * @param pattern the path pattern to match against
190         * @return a predicate that matches if the request method is POST and if the given pattern
191         * matches against the request path
192         */
193        public static RequestPredicate POST(String pattern) {
194                return method(HttpMethod.POST).and(path(pattern));
195        }
196
197        /**
198         * Return a {@code RequestPredicate} that matches if request's HTTP method is {@code PUT}
199         * and the given {@code pattern} matches against the request path.
200         * @param pattern the path pattern to match against
201         * @return a predicate that matches if the request method is PUT and if the given pattern
202         * matches against the request path
203         */
204        public static RequestPredicate PUT(String pattern) {
205                return method(HttpMethod.PUT).and(path(pattern));
206        }
207
208        /**
209         * Return a {@code RequestPredicate} that matches if request's HTTP method is {@code PATCH}
210         * and the given {@code pattern} matches against the request path.
211         * @param pattern the path pattern to match against
212         * @return a predicate that matches if the request method is PATCH and if the given pattern
213         * matches against the request path
214         */
215        public static RequestPredicate PATCH(String pattern) {
216                return method(HttpMethod.PATCH).and(path(pattern));
217        }
218
219        /**
220         * Return a {@code RequestPredicate} that matches if request's HTTP method is {@code DELETE}
221         * and the given {@code pattern} matches against the request path.
222         * @param pattern the path pattern to match against
223         * @return a predicate that matches if the request method is DELETE and if the given pattern
224         * matches against the request path
225         */
226        public static RequestPredicate DELETE(String pattern) {
227                return method(HttpMethod.DELETE).and(path(pattern));
228        }
229
230        /**
231         * Return a {@code RequestPredicate} that matches if request's HTTP method is {@code OPTIONS}
232         * and the given {@code pattern} matches against the request path.
233         * @param pattern the path pattern to match against
234         * @return a predicate that matches if the request method is OPTIONS and if the given pattern
235         * matches against the request path
236         */
237        public static RequestPredicate OPTIONS(String pattern) {
238                return method(HttpMethod.OPTIONS).and(path(pattern));
239        }
240
241        /**
242         * Return a {@code RequestPredicate} that matches if the request's path has the given extension.
243         * @param extension the path extension to match against, ignoring case
244         * @return a predicate that matches if the request's path has the given file extension
245         */
246        public static RequestPredicate pathExtension(String extension) {
247                Assert.notNull(extension, "'extension' must not be null");
248                return new PathExtensionPredicate(extension);
249        }
250
251        /**
252         * Return a {@code RequestPredicate} that matches if the request's path matches the given
253         * predicate.
254         * @param extensionPredicate the predicate to test against the request path extension
255         * @return a predicate that matches if the given predicate matches against the request's path
256         * file extension
257         */
258        public static RequestPredicate pathExtension(Predicate<String> extensionPredicate) {
259                return new PathExtensionPredicate(extensionPredicate);
260        }
261
262        /**
263         * Return a {@code RequestPredicate} that matches if the request's parameter of the given name
264         * has the given value.
265         * @param name the name of the parameter to test against
266         * @param value the value of the parameter to test against
267         * @return a predicate that matches if the parameter has the given value
268         * @see ServerRequest#param(String)
269         */
270        public static RequestPredicate param(String name, String value) {
271                return new ParamPredicate(name, value);
272        }
273
274        /**
275         * Return a {@code RequestPredicate} that tests the request's parameter of the given name
276         * against the given predicate.
277         * @param name the name of the parameter to test against
278         * @param predicate the predicate to test against the parameter value
279         * @return a predicate that matches the given predicate against the parameter of the given name
280         * @see ServerRequest#param(String)
281         */
282        public static RequestPredicate param(String name, Predicate<String> predicate) {
283                return new ParamPredicate(name, predicate);
284        }
285
286
287        private static void traceMatch(String prefix, Object desired, @Nullable Object actual, boolean match) {
288                if (logger.isTraceEnabled()) {
289                        logger.trace(String.format("%s \"%s\" %s against value \"%s\"",
290                                        prefix, desired, match ? "matches" : "does not match", actual));
291                }
292        }
293
294        private static void restoreAttributes(ServerRequest request, Map<String, Object> attributes) {
295                request.attributes().clear();
296                request.attributes().putAll(attributes);
297        }
298
299        private static Map<String, String> mergePathVariables(Map<String, String> oldVariables,
300                        Map<String, String> newVariables) {
301
302                if (!newVariables.isEmpty()) {
303                        Map<String, String> mergedVariables = new LinkedHashMap<>(oldVariables);
304                        mergedVariables.putAll(newVariables);
305                        return mergedVariables;
306                }
307                else {
308                        return oldVariables;
309                }
310        }
311
312        private static PathPattern mergePatterns(@Nullable PathPattern oldPattern, PathPattern newPattern) {
313                if (oldPattern != null) {
314                        return oldPattern.combine(newPattern);
315                }
316                else {
317                        return newPattern;
318                }
319
320        }
321
322
323        /**
324         * Receives notifications from the logical structure of request predicates.
325         */
326        public interface Visitor {
327
328                /**
329                 * Receive notification of an HTTP method predicate.
330                 * @param methods the HTTP methods that make up the predicate
331                 * @see RequestPredicates#method(HttpMethod)
332                 */
333                void method(Set<HttpMethod> methods);
334
335                /**
336                 * Receive notification of an path predicate.
337                 * @param pattern the path pattern that makes up the predicate
338                 * @see RequestPredicates#path(String)
339                 */
340                void path(String pattern);
341
342                /**
343                 * Receive notification of an path extension predicate.
344                 * @param extension the path extension that makes up the predicate
345                 * @see RequestPredicates#pathExtension(String)
346                 */
347                void pathExtension(String extension);
348
349                /**
350                 * Receive notification of an HTTP header predicate.
351                 * @param name the name of the HTTP header to check
352                 * @param value the desired value of the HTTP header
353                 * @see RequestPredicates#headers(Predicate)
354                 * @see RequestPredicates#contentType(MediaType...)
355                 * @see RequestPredicates#accept(MediaType...)
356                 */
357                void header(String name, String value);
358
359                /**
360                 * Receive notification of a parameter predicate.
361                 * @param name the name of the parameter
362                 * @param value the desired value of the parameter
363                 * @see RequestPredicates#param(String, String)
364                 */
365                void param(String name, String value);
366
367                /**
368                 * Receive first notification of a logical AND predicate.
369                 * The first subsequent notification will contain the left-hand side of the AND-predicate;
370                 * followed by {@link #and()}, followed by the right-hand side, followed by {@link #endAnd()}.
371                 * @see RequestPredicate#and(RequestPredicate)
372                 */
373                void startAnd();
374
375                /**
376                 * Receive "middle" notification of a logical AND predicate.
377                 * The following notification contains the right-hand side, followed by {@link #endAnd()}.
378                 * @see RequestPredicate#and(RequestPredicate)
379                 */
380                void and();
381
382                /**
383                 * Receive last notification of a logical AND predicate.
384                 * @see RequestPredicate#and(RequestPredicate)
385                 */
386                void endAnd();
387
388                /**
389                 * Receive first notification of a logical OR predicate.
390                 * The first subsequent notification will contain the left-hand side of the OR-predicate;
391                 * the second notification contains the right-hand side, followed by {@link #endOr()}.
392                 * @see RequestPredicate#or(RequestPredicate)
393                 */
394                void startOr();
395
396                /**
397                 * Receive "middle" notification of a logical OR predicate.
398                 * The following notification contains the right-hand side, followed by {@link #endOr()}.
399                 * @see RequestPredicate#or(RequestPredicate)
400                 */
401                void or();
402
403                /**
404                 * Receive last notification of a logical OR predicate.
405                 * @see RequestPredicate#or(RequestPredicate)
406                 */
407                void endOr();
408
409                /**
410                 * Receive first notification of a negated predicate.
411                 * The first subsequent notification will contain the negated predicated, followed
412                 * by {@link #endNegate()}.
413                 * @see RequestPredicate#negate()
414                 */
415                void startNegate();
416
417                /**
418                 * Receive last notification of a negated predicate.
419                 * @see RequestPredicate#negate()
420                 */
421                void endNegate();
422
423                /**
424                 * Receive first notification of an unknown predicate.
425                 */
426                void unknown(RequestPredicate predicate);
427        }
428
429        private static class HttpMethodPredicate implements RequestPredicate {
430
431                private final Set<HttpMethod> httpMethods;
432
433
434                public HttpMethodPredicate(HttpMethod httpMethod) {
435                        Assert.notNull(httpMethod, "HttpMethod must not be null");
436                        this.httpMethods = EnumSet.of(httpMethod);
437                }
438
439                public HttpMethodPredicate(HttpMethod... httpMethods) {
440                        Assert.notEmpty(httpMethods, "HttpMethods must not be empty");
441                        this.httpMethods = EnumSet.copyOf(Arrays.asList(httpMethods));
442                }
443
444                @Override
445                public boolean test(ServerRequest request) {
446                        HttpMethod method = method(request);
447                        boolean match = this.httpMethods.contains(method);
448                        traceMatch("Method", this.httpMethods, method, match);
449                        return match;
450                }
451
452                @Nullable
453                private static HttpMethod method(ServerRequest request) {
454                        if (CorsUtils.isPreFlightRequest(request.servletRequest())) {
455                                String accessControlRequestMethod =
456                                                request.headers().firstHeader(HttpHeaders.ACCESS_CONTROL_REQUEST_METHOD);
457                                return HttpMethod.resolve(accessControlRequestMethod);
458                        }
459                        else {
460                                return request.method();
461                        }
462                }
463
464                @Override
465                public void accept(Visitor visitor) {
466                        visitor.method(Collections.unmodifiableSet(this.httpMethods));
467                }
468
469                @Override
470                public String toString() {
471                        if (this.httpMethods.size() == 1) {
472                                return this.httpMethods.iterator().next().toString();
473                        }
474                        else {
475                                return this.httpMethods.toString();
476                        }
477                }
478        }
479
480
481        private static class PathPatternPredicate implements RequestPredicate {
482
483                private final PathPattern pattern;
484
485                public PathPatternPredicate(PathPattern pattern) {
486                        Assert.notNull(pattern, "'pattern' must not be null");
487                        this.pattern = pattern;
488                }
489
490                @Override
491                public boolean test(ServerRequest request) {
492                        PathContainer pathContainer = request.pathContainer();
493                        PathPattern.PathMatchInfo info = this.pattern.matchAndExtract(pathContainer);
494                        traceMatch("Pattern", this.pattern.getPatternString(), request.path(), info != null);
495                        if (info != null) {
496                                mergeAttributes(request, info.getUriVariables(), this.pattern);
497                                return true;
498                        }
499                        else {
500                                return false;
501                        }
502                }
503
504                private static void mergeAttributes(ServerRequest request, Map<String, String> variables,
505                                PathPattern pattern) {
506                        Map<String, String> pathVariables = mergePathVariables(request.pathVariables(), variables);
507                        request.attributes().put(RouterFunctions.URI_TEMPLATE_VARIABLES_ATTRIBUTE,
508                                                Collections.unmodifiableMap(pathVariables));
509
510                        pattern = mergePatterns(
511                                        (PathPattern) request.attributes().get(RouterFunctions.MATCHING_PATTERN_ATTRIBUTE),
512                                        pattern);
513                        request.attributes().put(RouterFunctions.MATCHING_PATTERN_ATTRIBUTE, pattern);
514                }
515                @Override
516                public Optional<ServerRequest> nest(ServerRequest request) {
517                        return Optional.ofNullable(this.pattern.matchStartOfPath(request.pathContainer()))
518                                        .map(info -> new SubPathServerRequestWrapper(request, info, this.pattern));
519                }
520
521                @Override
522                public void accept(Visitor visitor) {
523                        visitor.path(this.pattern.getPatternString());
524                }
525
526                @Override
527                public String toString() {
528                        return this.pattern.getPatternString();
529                }
530        }
531
532
533        private static class HeadersPredicate implements RequestPredicate {
534
535                private final Predicate<ServerRequest.Headers> headersPredicate;
536
537                public HeadersPredicate(Predicate<ServerRequest.Headers> headersPredicate) {
538                        Assert.notNull(headersPredicate, "Predicate must not be null");
539                        this.headersPredicate = headersPredicate;
540                }
541
542                @Override
543                public boolean test(ServerRequest request) {
544                        if (CorsUtils.isPreFlightRequest(request.servletRequest())) {
545                                return true;
546                        }
547                        else {
548                                return this.headersPredicate.test(request.headers());
549                        }
550                }
551
552                @Override
553                public String toString() {
554                        return this.headersPredicate.toString();
555                }
556        }
557
558        private static class ContentTypePredicate extends HeadersPredicate {
559
560                private final Set<MediaType> mediaTypes;
561
562                public ContentTypePredicate(MediaType... mediaTypes) {
563                        this(new HashSet<>(Arrays.asList(mediaTypes)));
564                }
565
566                private ContentTypePredicate(Set<MediaType> mediaTypes) {
567                        super(headers -> {
568                                MediaType contentType =
569                                                headers.contentType().orElse(MediaType.APPLICATION_OCTET_STREAM);
570                                boolean match = mediaTypes.stream()
571                                                .anyMatch(mediaType -> mediaType.includes(contentType));
572                                traceMatch("Content-Type", mediaTypes, contentType, match);
573                                return match;
574                        });
575                        this.mediaTypes = mediaTypes;
576                }
577
578                @Override
579                public void accept(Visitor visitor) {
580                        visitor.header(HttpHeaders.CONTENT_TYPE,
581                                        (this.mediaTypes.size() == 1) ?
582                                                        this.mediaTypes.iterator().next().toString() :
583                                                        this.mediaTypes.toString());
584                }
585
586                @Override
587                public String toString() {
588                        return String.format("Content-Type: %s",
589                                        (this.mediaTypes.size() == 1) ?
590                                                        this.mediaTypes.iterator().next().toString() :
591                                                        this.mediaTypes.toString());
592                }
593        }
594
595        private static class AcceptPredicate extends HeadersPredicate {
596
597                private final Set<MediaType> mediaTypes;
598
599                public AcceptPredicate(MediaType... mediaTypes) {
600                        this(new HashSet<>(Arrays.asList(mediaTypes)));
601                }
602
603                private AcceptPredicate(Set<MediaType> mediaTypes) {
604                        super(headers -> {
605                                List<MediaType> acceptedMediaTypes = acceptedMediaTypes(headers);
606                                boolean match = acceptedMediaTypes.stream()
607                                                .anyMatch(acceptedMediaType -> mediaTypes.stream()
608                                                                .anyMatch(acceptedMediaType::isCompatibleWith));
609                                traceMatch("Accept", mediaTypes, acceptedMediaTypes, match);
610                                return match;
611                        });
612                        this.mediaTypes = mediaTypes;
613                }
614
615                @NonNull
616                private static List<MediaType> acceptedMediaTypes(ServerRequest.Headers headers) {
617                        List<MediaType> acceptedMediaTypes = headers.accept();
618                        if (acceptedMediaTypes.isEmpty()) {
619                                acceptedMediaTypes = Collections.singletonList(MediaType.ALL);
620                        }
621                        else {
622                                MediaType.sortBySpecificityAndQuality(acceptedMediaTypes);
623                        }
624                        return acceptedMediaTypes;
625                }
626
627                @Override
628                public void accept(Visitor visitor) {
629                        visitor.header(HttpHeaders.ACCEPT,
630                                        (this.mediaTypes.size() == 1) ?
631                                                        this.mediaTypes.iterator().next().toString() :
632                                                        this.mediaTypes.toString());
633                }
634
635                @Override
636                public String toString() {
637                        return String.format("Accept: %s",
638                                        (this.mediaTypes.size() == 1) ?
639                                                        this.mediaTypes.iterator().next().toString() :
640                                                        this.mediaTypes.toString());
641                }
642        }
643
644        private static class PathExtensionPredicate implements RequestPredicate {
645
646                private final Predicate<String> extensionPredicate;
647
648                @Nullable
649                private final String extension;
650                public PathExtensionPredicate(Predicate<String> extensionPredicate) {
651                        Assert.notNull(extensionPredicate, "Predicate must not be null");
652                        this.extensionPredicate = extensionPredicate;
653                        this.extension = null;
654                }
655
656                public PathExtensionPredicate(String extension) {
657                        Assert.notNull(extension, "Extension must not be null");
658
659                        this.extensionPredicate = s -> {
660                                boolean match = extension.equalsIgnoreCase(s);
661                                traceMatch("Extension", extension, s, match);
662                                return match;
663                        };
664                        this.extension = extension;
665                }
666
667                @Override
668                public boolean test(ServerRequest request) {
669                        String pathExtension = UriUtils.extractFileExtension(request.path());
670                        return this.extensionPredicate.test(pathExtension);
671                }
672
673                @Override
674                public void accept(Visitor visitor) {
675                        visitor.pathExtension(
676                                        (this.extension != null) ?
677                                                        this.extension :
678                                                        this.extensionPredicate.toString());
679                }
680
681                @Override
682                public String toString() {
683                        return String.format("*.%s",
684                                        (this.extension != null) ?
685                                                        this.extension :
686                                                        this.extensionPredicate);
687                }
688
689        }
690
691
692        private static class ParamPredicate implements RequestPredicate {
693
694                private final String name;
695
696                private final Predicate<String> valuePredicate;
697
698                @Nullable
699                private final String value;
700
701                public ParamPredicate(String name, Predicate<String> valuePredicate) {
702                        Assert.notNull(name, "Name must not be null");
703                        Assert.notNull(valuePredicate, "Predicate must not be null");
704                        this.name = name;
705                        this.valuePredicate = valuePredicate;
706                        this.value = null;
707                }
708
709                public ParamPredicate(String name, String value) {
710                        Assert.notNull(name, "Name must not be null");
711                        Assert.notNull(value, "Value must not be null");
712                        this.name = name;
713                        this.valuePredicate = value::equals;
714                        this.value = value;
715                }
716
717                @Override
718                public boolean test(ServerRequest request) {
719                        Optional<String> s = request.param(this.name);
720                        return s.filter(this.valuePredicate).isPresent();
721                }
722
723                @Override
724                public void accept(Visitor visitor) {
725                        visitor.param(this.name,
726                                        (this.value != null) ?
727                                                        this.value :
728                                                        this.valuePredicate.toString());
729                }
730
731                @Override
732                public String toString() {
733                        return String.format("?%s %s", this.name,
734                                        (this.value != null) ?
735                                                        this.value :
736                                                        this.valuePredicate);
737                }
738        }
739
740
741        /**
742         * {@link RequestPredicate} for where both {@code left} and {@code right} predicates
743         * must match.
744         */
745        static class AndRequestPredicate implements RequestPredicate {
746
747                private final RequestPredicate left;
748
749                private final RequestPredicate right;
750
751                public AndRequestPredicate(RequestPredicate left, RequestPredicate right) {
752                        Assert.notNull(left, "Left RequestPredicate must not be null");
753                        Assert.notNull(right, "Right RequestPredicate must not be null");
754                        this.left = left;
755                        this.right = right;
756                }
757
758                @Override
759                public boolean test(ServerRequest request) {
760                        Map<String, Object> oldAttributes = new HashMap<>(request.attributes());
761
762                        if (this.left.test(request) && this.right.test(request)) {
763                                return true;
764                        }
765                        restoreAttributes(request, oldAttributes);
766                        return false;
767                }
768
769                @Override
770                public Optional<ServerRequest> nest(ServerRequest request) {
771                        return this.left.nest(request).flatMap(this.right::nest);
772                }
773
774                @Override
775                public void accept(Visitor visitor) {
776                        visitor.startAnd();
777                        this.left.accept(visitor);
778                        visitor.and();
779                        this.right.accept(visitor);
780                        visitor.endAnd();
781                }
782
783                @Override
784                public String toString() {
785                        return String.format("(%s && %s)", this.left, this.right);
786                }
787        }
788
789        /**
790         * {@link RequestPredicate} that negates a delegate predicate.
791         */
792        static class NegateRequestPredicate implements RequestPredicate {
793                private final RequestPredicate delegate;
794
795                public NegateRequestPredicate(RequestPredicate delegate) {
796                        Assert.notNull(delegate, "Delegate must not be null");
797                        this.delegate = delegate;
798                }
799
800                @Override
801                public boolean test(ServerRequest request) {
802                        Map<String, Object> oldAttributes = new HashMap<>(request.attributes());
803                        boolean result = !this.delegate.test(request);
804                        if (!result) {
805                                restoreAttributes(request, oldAttributes);
806                        }
807                        return result;
808                }
809
810                @Override
811                public void accept(Visitor visitor) {
812                        visitor.startNegate();
813                        this.delegate.accept(visitor);
814                        visitor.endNegate();
815                }
816
817                @Override
818                public String toString() {
819                        return "!" + this.delegate.toString();
820                }
821        }
822
823        /**
824         * {@link RequestPredicate} where either {@code left} or {@code right} predicates
825         * may match.
826         */
827        static class OrRequestPredicate implements RequestPredicate {
828
829                private final RequestPredicate left;
830
831                private final RequestPredicate right;
832
833                public OrRequestPredicate(RequestPredicate left, RequestPredicate right) {
834                        Assert.notNull(left, "Left RequestPredicate must not be null");
835                        Assert.notNull(right, "Right RequestPredicate must not be null");
836                        this.left = left;
837                        this.right = right;
838                }
839
840                @Override
841                public boolean test(ServerRequest request) {
842                        Map<String, Object> oldAttributes = new HashMap<>(request.attributes());
843
844                        if (this.left.test(request)) {
845                                return true;
846                        }
847                        else {
848                                restoreAttributes(request, oldAttributes);
849                                if (this.right.test(request)) {
850                                        return true;
851                                }
852                        }
853                        restoreAttributes(request, oldAttributes);
854                        return false;
855                }
856
857                @Override
858                public Optional<ServerRequest> nest(ServerRequest request) {
859                        Optional<ServerRequest> leftResult = this.left.nest(request);
860                        if (leftResult.isPresent()) {
861                                return leftResult;
862                        }
863                        else {
864                                return this.right.nest(request);
865                        }
866                }
867
868                @Override
869                public void accept(Visitor visitor) {
870                        visitor.startOr();
871                        this.left.accept(visitor);
872                        visitor.or();
873                        this.right.accept(visitor);
874                        visitor.endOr();
875                }
876
877                @Override
878                public String toString() {
879                        return String.format("(%s || %s)", this.left, this.right);
880                }
881        }
882
883
884        private static class SubPathServerRequestWrapper implements ServerRequest {
885
886                private final ServerRequest request;
887
888                private final PathContainer pathContainer;
889
890                private final Map<String, Object> attributes;
891
892                public SubPathServerRequestWrapper(ServerRequest request,
893                                PathPattern.PathRemainingMatchInfo info, PathPattern pattern) {
894                        this.request = request;
895                        this.pathContainer = new SubPathContainer(info.getPathRemaining());
896                        this.attributes = mergeAttributes(request, info.getUriVariables(), pattern);
897                }
898
899                private static Map<String, Object> mergeAttributes(ServerRequest request,
900                Map<String, String> pathVariables, PathPattern pattern) {
901                        Map<String, Object> result = new ConcurrentHashMap<>(request.attributes());
902
903                        result.put(RouterFunctions.URI_TEMPLATE_VARIABLES_ATTRIBUTE,
904                                        mergePathVariables(request.pathVariables(), pathVariables));
905
906                        pattern = mergePatterns(
907                                        (PathPattern) request.attributes().get(RouterFunctions.MATCHING_PATTERN_ATTRIBUTE),
908                                        pattern);
909                        result.put(RouterFunctions.MATCHING_PATTERN_ATTRIBUTE, pattern);
910                        return result;
911                }
912
913                @Override
914                public HttpMethod method() {
915                        return this.request.method();
916                }
917
918                @Override
919                public String methodName() {
920                        return this.request.methodName();
921                }
922
923                @Override
924                public URI uri() {
925                        return this.request.uri();
926                }
927
928                @Override
929                public UriBuilder uriBuilder() {
930                        return this.request.uriBuilder();
931                }
932
933                @Override
934                public String path() {
935                        return this.pathContainer.value();
936                }
937
938                @Override
939                public PathContainer pathContainer() {
940                        return this.pathContainer;
941                }
942
943                @Override
944                public Headers headers() {
945                        return this.request.headers();
946                }
947
948                @Override
949                public MultiValueMap<String, Cookie> cookies() {
950                        return this.request.cookies();
951                }
952
953                @Override
954                public Optional<InetSocketAddress> remoteAddress() {
955                        return this.request.remoteAddress();
956                }
957
958                @Override
959                public List<HttpMessageConverter<?>> messageConverters() {
960                        return this.request.messageConverters();
961                }
962
963                @Override
964                public <T> T body(Class<T> bodyType) throws ServletException, IOException {
965                        return this.request.body(bodyType);
966                }
967
968                @Override
969                public <T> T body(ParameterizedTypeReference<T> bodyType)
970                                throws ServletException, IOException {
971                        return this.request.body(bodyType);
972                }
973
974                @Override
975                public Optional<Object> attribute(String name) {
976                        return this.request.attribute(name);
977                }
978
979                @Override
980                public Map<String, Object> attributes() {
981                        return this.attributes;
982                }
983
984                @Override
985                public Optional<String> param(String name) {
986                        return this.request.param(name);
987                }
988
989                @Override
990                public MultiValueMap<String, String> params() {
991                        return this.request.params();
992                }
993
994                @Override
995                @SuppressWarnings("unchecked")
996                public Map<String, String> pathVariables() {
997                        return (Map<String, String>) this.attributes.getOrDefault(
998                                        RouterFunctions.URI_TEMPLATE_VARIABLES_ATTRIBUTE, Collections.emptyMap());
999                }
1000
1001                @Override
1002                public HttpSession session() {
1003                        return this.request.session();
1004                }
1005
1006
1007
1008                @Override
1009                public Optional<Principal> principal() {
1010                        return this.request.principal();
1011                }
1012
1013                @Override
1014                public HttpServletRequest servletRequest() {
1015                        return this.request.servletRequest();
1016                }
1017
1018                @Override
1019                public Optional<ServerResponse> checkNotModified(Instant lastModified) {
1020                        return this.request.checkNotModified(lastModified);
1021                }
1022
1023                @Override
1024                public Optional<ServerResponse> checkNotModified(String etag) {
1025                        return this.request.checkNotModified(etag);
1026                }
1027
1028                @Override
1029                public Optional<ServerResponse> checkNotModified(Instant lastModified, String etag) {
1030                        return this.request.checkNotModified(lastModified, etag);
1031                }
1032
1033                @Override
1034                public String toString() {
1035                        return method() + " " +  path();
1036                }
1037
1038                private static class SubPathContainer implements PathContainer {
1039
1040                        private static final PathContainer.Separator SEPARATOR = () -> "/";
1041
1042
1043                        private final String value;
1044
1045                        private final List<Element> elements;
1046
1047                        public SubPathContainer(PathContainer original) {
1048                                this.value = prefixWithSlash(original.value());
1049                                this.elements = prependWithSeparator(original.elements());
1050                        }
1051
1052                        private static String prefixWithSlash(String path) {
1053                                if (!path.startsWith("/")) {
1054                                        path = "/" + path;
1055                                }
1056                                return path;
1057                        }
1058
1059                        private static List<Element> prependWithSeparator(List<Element> elements) {
1060                                List<Element> result = new ArrayList<>(elements);
1061                                if (result.isEmpty() || !(result.get(0) instanceof Separator)) {
1062                                        result.add(0, SEPARATOR);
1063                                }
1064                                return Collections.unmodifiableList(result);
1065                        }
1066
1067
1068                        @Override
1069                        public String value() {
1070                                return this.value;
1071                        }
1072
1073                        @Override
1074                        public List<Element> elements() {
1075                                return this.elements;
1076                        }
1077                }
1078        }
1079
1080}