001/*
002 * Copyright 2002-2019 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.util.pattern;
018
019import java.util.Comparator;
020import java.util.Map;
021import java.util.concurrent.ConcurrentHashMap;
022
023import org.springframework.http.server.PathContainer;
024import org.springframework.lang.Nullable;
025import org.springframework.util.Assert;
026import org.springframework.util.RouteMatcher;
027
028/**
029 * {@code RouteMatcher} built on {@link PathPatternParser} that uses
030 * {@link PathContainer} and {@link PathPattern} as parsed representations of
031 * routes and patterns.
032 *
033 * @author Rossen Stoyanchev
034 * @since 5.2
035 */
036public class PathPatternRouteMatcher implements RouteMatcher {
037
038        private final PathPatternParser parser;
039
040        private final Map<String, PathPattern> pathPatternCache = new ConcurrentHashMap<>();
041
042
043        /**
044         * Default constructor with {@link PathPatternParser} customized for
045         * {@link org.springframework.http.server.PathContainer.Options#MESSAGE_ROUTE MESSAGE_ROUTE}
046         * and without matching of trailing separator.
047         */
048        public PathPatternRouteMatcher() {
049                this.parser = new PathPatternParser();
050                this.parser.setPathOptions(PathContainer.Options.MESSAGE_ROUTE);
051                this.parser.setMatchOptionalTrailingSeparator(false);
052        }
053
054        /**
055         * Constructor with given {@link PathPatternParser}.
056         */
057        public PathPatternRouteMatcher(PathPatternParser parser) {
058                Assert.notNull(parser, "PathPatternParser must not be null");
059                this.parser = parser;
060        }
061
062
063        @Override
064        public Route parseRoute(String routeValue) {
065                return new PathContainerRoute(PathContainer.parsePath(routeValue, this.parser.getPathOptions()));
066        }
067
068        @Override
069        public boolean isPattern(String route) {
070                return getPathPattern(route).hasPatternSyntax();
071        }
072
073        @Override
074        public String combine(String pattern1, String pattern2) {
075                return getPathPattern(pattern1).combine(getPathPattern(pattern2)).getPatternString();
076        }
077
078        @Override
079        public boolean match(String pattern, Route route) {
080                return getPathPattern(pattern).matches(getPathContainer(route));
081        }
082
083        @Override
084        @Nullable
085        public Map<String, String> matchAndExtract(String pattern, Route route) {
086                PathPattern.PathMatchInfo info = getPathPattern(pattern).matchAndExtract(getPathContainer(route));
087                return info != null ? info.getUriVariables() : null;
088        }
089
090        @Override
091        public Comparator<String> getPatternComparator(Route route) {
092                return Comparator.comparing(this::getPathPattern);
093        }
094
095        private PathPattern getPathPattern(String pattern) {
096                return this.pathPatternCache.computeIfAbsent(pattern, this.parser::parse);
097        }
098
099        private PathContainer getPathContainer(Route route) {
100                Assert.isInstanceOf(PathContainerRoute.class, route);
101                return ((PathContainerRoute) route).pathContainer;
102        }
103
104
105        private static class PathContainerRoute implements Route {
106
107                private final PathContainer pathContainer;
108
109
110                PathContainerRoute(PathContainer pathContainer) {
111                        this.pathContainer = pathContainer;
112                }
113
114
115                @Override
116                public String value() {
117                        return this.pathContainer.value();
118                }
119
120
121                @Override
122                public String toString() {
123                        return value();
124                }
125        }
126
127}