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.util.pattern; 018 019import org.apache.commons.logging.Log; 020import org.apache.commons.logging.LogFactory; 021 022import org.springframework.http.server.PathContainer; 023 024/** 025 * Parser for URI path patterns producing {@link PathPattern} instances that can 026 * then be matched to requests. 027 * 028 * <p>The {@link PathPatternParser} and {@link PathPattern} are specifically 029 * designed for use with HTTP URL paths in web applications where a large number 030 * of URI path patterns, continuously matched against incoming requests, 031 * motivates the need for efficient matching. 032 * 033 * <p>For details of the path pattern syntax see {@link PathPattern}. 034 * 035 * @author Andy Clement 036 * @since 5.0 037 */ 038public class PathPatternParser { 039 040 private static final Log logger = LogFactory.getLog(PathPatternParser.class); 041 042 private boolean matchOptionalTrailingSeparator = true; 043 044 private boolean caseSensitive = true; 045 046 private PathContainer.Options pathOptions = PathContainer.Options.HTTP_PATH; 047 048 049 /** 050 * Whether a {@link PathPattern} produced by this parser should 051 * automatically match request paths with a trailing slash. 052 * 053 * <p>If set to {@code true} a {@code PathPattern} without a trailing slash 054 * will also match request paths with a trailing slash. If set to 055 * {@code false} a {@code PathPattern} will only match request paths with 056 * a trailing slash. 057 * 058 * <p>The default is {@code true}. 059 */ 060 public void setMatchOptionalTrailingSeparator(boolean matchOptionalTrailingSeparator) { 061 this.matchOptionalTrailingSeparator = matchOptionalTrailingSeparator; 062 } 063 064 /** 065 * Whether optional trailing slashing match is enabled. 066 */ 067 public boolean isMatchOptionalTrailingSeparator() { 068 return this.matchOptionalTrailingSeparator; 069 } 070 071 /** 072 * Whether path pattern matching should be case-sensitive. 073 * <p>The default is {@code true}. 074 */ 075 public void setCaseSensitive(boolean caseSensitive) { 076 this.caseSensitive = caseSensitive; 077 } 078 079 /** 080 * Whether case-sensitive pattern matching is enabled. 081 */ 082 public boolean isCaseSensitive() { 083 return this.caseSensitive; 084 } 085 086 /** 087 * Set options for parsing patterns. These should be the same as the 088 * options used to parse input paths. 089 * <p>{@link org.springframework.http.server.PathContainer.Options#HTTP_PATH} 090 * is used by default. 091 * @since 5.2 092 */ 093 public void setPathOptions(PathContainer.Options pathOptions) { 094 this.pathOptions = pathOptions; 095 } 096 097 /** 098 * Return the {@link #setPathOptions configured} pattern parsing options. 099 * @since 5.2 100 */ 101 public PathContainer.Options getPathOptions() { 102 return this.pathOptions; 103 } 104 105 106 /** 107 * Process the path pattern content, a character at a time, breaking it into 108 * path elements around separator boundaries and verifying the structure at each 109 * stage. Produces a PathPattern object that can be used for fast matching 110 * against paths. Each invocation of this method delegates to a new instance of 111 * the {@link InternalPathPatternParser} because that class is not thread-safe. 112 * @param pathPattern the input path pattern, e.g. /project/{name} 113 * @return a PathPattern for quickly matching paths against request paths 114 * @throws PatternParseException in case of parse errors 115 */ 116 public PathPattern parse(String pathPattern) throws PatternParseException { 117 int wildcardIndex = pathPattern.indexOf("**" + this.pathOptions.separator()); 118 if (wildcardIndex != -1 && wildcardIndex != pathPattern.length() - 3) { 119 logger.warn("'**' patterns are not supported in the middle of patterns and will be rejected in the future. " + 120 "Consider using '*' instead for matching a single path segment."); 121 } 122 return new InternalPathPatternParser(this).parse(pathPattern); 123 } 124 125 126 /** 127 * Shared, read-only instance of {@code PathPatternParser}. Uses default settings: 128 * <ul> 129 * <li>{@code matchOptionalTrailingSeparator=true} 130 * <li>{@code caseSensitivetrue} 131 * <li>{@code pathOptions=PathContainer.Options.HTTP_PATH} 132 * </ul> 133 */ 134 public final static PathPatternParser defaultInstance = new PathPatternParser() { 135 136 @Override 137 public void setMatchOptionalTrailingSeparator(boolean matchOptionalTrailingSeparator) { 138 raiseError(); 139 } 140 141 @Override 142 public void setCaseSensitive(boolean caseSensitive) { 143 raiseError(); 144 } 145 146 @Override 147 public void setPathOptions(PathContainer.Options pathOptions) { 148 raiseError(); 149 } 150 151 private void raiseError() { 152 throw new UnsupportedOperationException( 153 "This is a read-only, shared instance that cannot be modified"); 154 } 155 }; 156}