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}