1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17 package org.springframework.web.servlet.mvc.method;
18
19 import java.util.ArrayList;
20 import java.util.Collections;
21 import java.util.Comparator;
22 import java.util.HashSet;
23 import java.util.LinkedHashMap;
24 import java.util.LinkedHashSet;
25 import java.util.Map;
26 import java.util.Map.Entry;
27 import java.util.Set;
28 import javax.servlet.ServletException;
29 import javax.servlet.http.HttpServletRequest;
30
31 import org.springframework.http.InvalidMediaTypeException;
32 import org.springframework.http.MediaType;
33 import org.springframework.util.CollectionUtils;
34 import org.springframework.util.MultiValueMap;
35 import org.springframework.util.StringUtils;
36 import org.springframework.web.HttpMediaTypeNotAcceptableException;
37 import org.springframework.web.HttpMediaTypeNotSupportedException;
38 import org.springframework.web.HttpRequestMethodNotSupportedException;
39 import org.springframework.web.bind.UnsatisfiedServletRequestParameterException;
40 import org.springframework.web.bind.annotation.RequestMethod;
41 import org.springframework.web.method.HandlerMethod;
42 import org.springframework.web.servlet.HandlerMapping;
43 import org.springframework.web.servlet.handler.AbstractHandlerMethodMapping;
44 import org.springframework.web.servlet.mvc.condition.NameValueExpression;
45 import org.springframework.web.servlet.mvc.condition.ParamsRequestCondition;
46 import org.springframework.web.util.WebUtils;
47
48
49
50
51
52
53
54
55
56 public abstract class RequestMappingInfoHandlerMapping extends AbstractHandlerMethodMapping<RequestMappingInfo> {
57
58 protected RequestMappingInfoHandlerMapping() {
59 setHandlerMethodMappingNamingStrategy(new RequestMappingInfoHandlerMethodMappingNamingStrategy());
60 }
61
62
63
64
65
66 @Override
67 protected Set<String> getMappingPathPatterns(RequestMappingInfo info) {
68 return info.getPatternsCondition().getPatterns();
69 }
70
71
72
73
74
75
76
77 @Override
78 protected RequestMappingInfo getMatchingMapping(RequestMappingInfo info, HttpServletRequest request) {
79 return info.getMatchingCondition(request);
80 }
81
82
83
84
85 @Override
86 protected Comparator<RequestMappingInfo> getMappingComparator(final HttpServletRequest request) {
87 return new Comparator<RequestMappingInfo>() {
88 @Override
89 public int compare(RequestMappingInfo info1, RequestMappingInfo info2) {
90 return info1.compareTo(info2, request);
91 }
92 };
93 }
94
95
96
97
98
99
100
101 @Override
102 protected void handleMatch(RequestMappingInfo info, String lookupPath, HttpServletRequest request) {
103 super.handleMatch(info, lookupPath, request);
104
105 String bestPattern;
106 Map<String, String> uriVariables;
107 Map<String, String> decodedUriVariables;
108
109 Set<String> patterns = info.getPatternsCondition().getPatterns();
110 if (patterns.isEmpty()) {
111 bestPattern = lookupPath;
112 uriVariables = Collections.emptyMap();
113 decodedUriVariables = Collections.emptyMap();
114 }
115 else {
116 bestPattern = patterns.iterator().next();
117 uriVariables = getPathMatcher().extractUriTemplateVariables(bestPattern, lookupPath);
118 decodedUriVariables = getUrlPathHelper().decodePathVariables(request, uriVariables);
119 }
120
121 request.setAttribute(BEST_MATCHING_PATTERN_ATTRIBUTE, bestPattern);
122 request.setAttribute(HandlerMapping.URI_TEMPLATE_VARIABLES_ATTRIBUTE, decodedUriVariables);
123
124 if (isMatrixVariableContentAvailable()) {
125 Map<String, MultiValueMap<String, String>> matrixVars = extractMatrixVariables(request, uriVariables);
126 request.setAttribute(HandlerMapping.MATRIX_VARIABLES_ATTRIBUTE, matrixVars);
127 }
128
129 if (!info.getProducesCondition().getProducibleMediaTypes().isEmpty()) {
130 Set<MediaType> mediaTypes = info.getProducesCondition().getProducibleMediaTypes();
131 request.setAttribute(PRODUCIBLE_MEDIA_TYPES_ATTRIBUTE, mediaTypes);
132 }
133 }
134
135 private boolean isMatrixVariableContentAvailable() {
136 return !getUrlPathHelper().shouldRemoveSemicolonContent();
137 }
138
139 private Map<String, MultiValueMap<String, String>> extractMatrixVariables(
140 HttpServletRequest request, Map<String, String> uriVariables) {
141
142 Map<String, MultiValueMap<String, String>> result = new LinkedHashMap<String, MultiValueMap<String, String>>();
143 for (Entry<String, String> uriVar : uriVariables.entrySet()) {
144 String uriVarValue = uriVar.getValue();
145
146 int equalsIndex = uriVarValue.indexOf('=');
147 if (equalsIndex == -1) {
148 continue;
149 }
150
151 String matrixVariables;
152
153 int semicolonIndex = uriVarValue.indexOf(';');
154 if ((semicolonIndex == -1) || (semicolonIndex == 0) || (equalsIndex < semicolonIndex)) {
155 matrixVariables = uriVarValue;
156 }
157 else {
158 matrixVariables = uriVarValue.substring(semicolonIndex + 1);
159 uriVariables.put(uriVar.getKey(), uriVarValue.substring(0, semicolonIndex));
160 }
161
162 MultiValueMap<String, String> vars = WebUtils.parseMatrixVariables(matrixVariables);
163 result.put(uriVar.getKey(), getUrlPathHelper().decodeMatrixVariables(request, vars));
164 }
165 return result;
166 }
167
168
169
170
171
172
173
174
175
176 @Override
177 protected HandlerMethod handleNoMatch(Set<RequestMappingInfo> requestMappingInfos,
178 String lookupPath, HttpServletRequest request) throws ServletException {
179
180 Set<String> allowedMethods = new LinkedHashSet<String>(4);
181
182 Set<RequestMappingInfo> patternMatches = new HashSet<RequestMappingInfo>();
183 Set<RequestMappingInfo> patternAndMethodMatches = new HashSet<RequestMappingInfo>();
184
185 for (RequestMappingInfo info : requestMappingInfos) {
186 if (info.getPatternsCondition().getMatchingCondition(request) != null) {
187 patternMatches.add(info);
188 if (info.getMethodsCondition().getMatchingCondition(request) != null) {
189 patternAndMethodMatches.add(info);
190 }
191 else {
192 for (RequestMethod method : info.getMethodsCondition().getMethods()) {
193 allowedMethods.add(method.name());
194 }
195 }
196 }
197 }
198
199 if (patternMatches.isEmpty()) {
200 return null;
201 }
202 else if (patternAndMethodMatches.isEmpty() && !allowedMethods.isEmpty()) {
203 throw new HttpRequestMethodNotSupportedException(request.getMethod(), allowedMethods);
204 }
205
206 Set<MediaType> consumableMediaTypes;
207 Set<MediaType> producibleMediaTypes;
208 Set<String> paramConditions;
209
210 if (patternAndMethodMatches.isEmpty()) {
211 consumableMediaTypes = getConsumableMediaTypes(request, patternMatches);
212 producibleMediaTypes = getProducibleMediaTypes(request, patternMatches);
213 paramConditions = getRequestParams(request, patternMatches);
214 }
215 else {
216 consumableMediaTypes = getConsumableMediaTypes(request, patternAndMethodMatches);
217 producibleMediaTypes = getProducibleMediaTypes(request, patternAndMethodMatches);
218 paramConditions = getRequestParams(request, patternAndMethodMatches);
219 }
220
221 if (!consumableMediaTypes.isEmpty()) {
222 MediaType contentType = null;
223 if (StringUtils.hasLength(request.getContentType())) {
224 try {
225 contentType = MediaType.parseMediaType(request.getContentType());
226 }
227 catch (InvalidMediaTypeException ex) {
228 throw new HttpMediaTypeNotSupportedException(ex.getMessage());
229 }
230 }
231 throw new HttpMediaTypeNotSupportedException(contentType, new ArrayList<MediaType>(consumableMediaTypes));
232 }
233 else if (!producibleMediaTypes.isEmpty()) {
234 throw new HttpMediaTypeNotAcceptableException(new ArrayList<MediaType>(producibleMediaTypes));
235 }
236 else if (!CollectionUtils.isEmpty(paramConditions)) {
237 String[] params = paramConditions.toArray(new String[paramConditions.size()]);
238 throw new UnsatisfiedServletRequestParameterException(params, request.getParameterMap());
239 }
240 else {
241 return null;
242 }
243 }
244
245 private Set<MediaType> getConsumableMediaTypes(HttpServletRequest request, Set<RequestMappingInfo> partialMatches) {
246 Set<MediaType> result = new HashSet<MediaType>();
247 for (RequestMappingInfo partialMatch : partialMatches) {
248 if (partialMatch.getConsumesCondition().getMatchingCondition(request) == null) {
249 result.addAll(partialMatch.getConsumesCondition().getConsumableMediaTypes());
250 }
251 }
252 return result;
253 }
254
255 private Set<MediaType> getProducibleMediaTypes(HttpServletRequest request, Set<RequestMappingInfo> partialMatches) {
256 Set<MediaType> result = new HashSet<MediaType>();
257 for (RequestMappingInfo partialMatch : partialMatches) {
258 if (partialMatch.getProducesCondition().getMatchingCondition(request) == null) {
259 result.addAll(partialMatch.getProducesCondition().getProducibleMediaTypes());
260 }
261 }
262 return result;
263 }
264
265 private Set<String> getRequestParams(HttpServletRequest request, Set<RequestMappingInfo> partialMatches) {
266 for (RequestMappingInfo partialMatch : partialMatches) {
267 ParamsRequestCondition condition = partialMatch.getParamsCondition();
268 if (!CollectionUtils.isEmpty(condition.getExpressions()) && (condition.getMatchingCondition(request) == null)) {
269 Set<String> expressions = new HashSet<String>();
270 for (NameValueExpression<String> expr : condition.getExpressions()) {
271 expressions.add(expr.toString());
272 }
273 return expressions;
274 }
275 }
276 return null;
277 }
278
279 }