20
20
import java .util .ArrayList ;
21
21
import java .util .Collections ;
22
22
import java .util .Comparator ;
23
- import java .util .HashSet ;
24
23
import java .util .LinkedHashMap ;
25
24
import java .util .LinkedHashSet ;
26
25
import java .util .List ;
46
45
import org .springframework .web .servlet .HandlerMapping ;
47
46
import org .springframework .web .servlet .handler .AbstractHandlerMethodMapping ;
48
47
import org .springframework .web .servlet .mvc .condition .NameValueExpression ;
49
- import org .springframework .web .servlet .mvc .condition .ParamsRequestCondition ;
50
48
import org .springframework .web .util .WebUtils ;
51
49
52
50
/**
@@ -183,65 +181,35 @@ private Map<String, MultiValueMap<String, String>> extractMatrixVariables(
183
181
}
184
182
185
183
/**
186
- * Iterate all RequestMappingInfos once again, look if any match by URL at
187
- * least and raise exceptions accordingly.
184
+ * Iterate all RequestMappingInfo's once again, look if any match by URL at
185
+ * least and raise exceptions according to what doesn't match.
186
+ *
188
187
* @throws HttpRequestMethodNotSupportedException if there are matches by URL
189
188
* but not by HTTP method
190
189
* @throws HttpMediaTypeNotAcceptableException if there are matches by URL
191
190
* but not by consumable/producible media types
192
191
*/
193
192
@ Override
194
- protected HandlerMethod handleNoMatch (Set <RequestMappingInfo > requestMappingInfos ,
195
- String lookupPath , HttpServletRequest request ) throws ServletException {
193
+ protected HandlerMethod handleNoMatch (Set <RequestMappingInfo > infos , String lookupPath ,
194
+ HttpServletRequest request ) throws ServletException {
196
195
197
- Set < String > allowedMethods = new LinkedHashSet < String >( 4 );
196
+ PartialMatchHelper helper = new PartialMatchHelper ( infos , request );
198
197
199
- Set <RequestMappingInfo > patternMatches = new HashSet <RequestMappingInfo >();
200
- Set <RequestMappingInfo > patternAndMethodMatches = new HashSet <RequestMappingInfo >();
201
-
202
- for (RequestMappingInfo info : requestMappingInfos ) {
203
- if (info .getPatternsCondition ().getMatchingCondition (request ) != null ) {
204
- patternMatches .add (info );
205
- if (info .getMethodsCondition ().getMatchingCondition (request ) != null ) {
206
- patternAndMethodMatches .add (info );
207
- }
208
- else {
209
- for (RequestMethod method : info .getMethodsCondition ().getMethods ()) {
210
- allowedMethods .add (method .name ());
211
- }
212
- }
213
- }
214
- }
215
-
216
- if (patternMatches .isEmpty ()) {
198
+ if (helper .isEmpty ()) {
217
199
return null ;
218
200
}
219
- else if (patternAndMethodMatches .isEmpty ()) {
201
+
202
+ if (helper .hasMethodsMismatch ()) {
203
+ Set <String > methods = helper .getAllowedMethods ();
220
204
if (HttpMethod .OPTIONS .matches (request .getMethod ())) {
221
- HttpOptionsHandler handler = new HttpOptionsHandler (allowedMethods );
205
+ HttpOptionsHandler handler = new HttpOptionsHandler (methods );
222
206
return new HandlerMethod (handler , HTTP_OPTIONS_HANDLE_METHOD );
223
207
}
224
- else if (!allowedMethods .isEmpty ()) {
225
- throw new HttpRequestMethodNotSupportedException (request .getMethod (), allowedMethods );
226
- }
208
+ throw new HttpRequestMethodNotSupportedException (request .getMethod (), methods );
227
209
}
228
210
229
- Set <MediaType > consumableMediaTypes ;
230
- Set <MediaType > producibleMediaTypes ;
231
- List <String []> paramConditions ;
232
-
233
- if (patternAndMethodMatches .isEmpty ()) {
234
- consumableMediaTypes = getConsumableMediaTypes (request , patternMatches );
235
- producibleMediaTypes = getProducibleMediaTypes (request , patternMatches );
236
- paramConditions = getRequestParams (request , patternMatches );
237
- }
238
- else {
239
- consumableMediaTypes = getConsumableMediaTypes (request , patternAndMethodMatches );
240
- producibleMediaTypes = getProducibleMediaTypes (request , patternAndMethodMatches );
241
- paramConditions = getRequestParams (request , patternAndMethodMatches );
242
- }
243
-
244
- if (!consumableMediaTypes .isEmpty ()) {
211
+ if (helper .hasConsumesMismatch ()) {
212
+ Set <MediaType > mediaTypes = helper .getConsumableMediaTypes ();
245
213
MediaType contentType = null ;
246
214
if (StringUtils .hasLength (request .getContentType ())) {
247
215
try {
@@ -251,56 +219,214 @@ else if (!allowedMethods.isEmpty()) {
251
219
throw new HttpMediaTypeNotSupportedException (ex .getMessage ());
252
220
}
253
221
}
254
- throw new HttpMediaTypeNotSupportedException (contentType , new ArrayList <MediaType >(consumableMediaTypes ));
255
- }
256
- else if (!producibleMediaTypes .isEmpty ()) {
257
- throw new HttpMediaTypeNotAcceptableException (new ArrayList <MediaType >(producibleMediaTypes ));
222
+ throw new HttpMediaTypeNotSupportedException (contentType , new ArrayList <MediaType >(mediaTypes ));
258
223
}
259
- else if (!CollectionUtils .isEmpty (paramConditions )) {
260
- throw new UnsatisfiedServletRequestParameterException (paramConditions , request .getParameterMap ());
224
+
225
+ if (helper .hasProducesMismatch ()) {
226
+ Set <MediaType > mediaTypes = helper .getProducibleMediaTypes ();
227
+ throw new HttpMediaTypeNotAcceptableException (new ArrayList <MediaType >(mediaTypes ));
261
228
}
262
- else {
263
- return null ;
229
+
230
+ if (helper .hasParamsMismatch ()) {
231
+ List <String []> conditions = helper .getParamConditions ();
232
+ throw new UnsatisfiedServletRequestParameterException (conditions , request .getParameterMap ());
264
233
}
234
+
235
+ return null ;
265
236
}
266
237
267
- private Set <MediaType > getConsumableMediaTypes (HttpServletRequest request , Set <RequestMappingInfo > partialMatches ) {
268
- Set <MediaType > result = new HashSet <MediaType >();
269
- for (RequestMappingInfo partialMatch : partialMatches ) {
270
- if (partialMatch .getConsumesCondition ().getMatchingCondition (request ) == null ) {
271
- result .addAll (partialMatch .getConsumesCondition ().getConsumableMediaTypes ());
238
+
239
+ /**
240
+ * Aggregate all partial matches and expose methods checking across them.
241
+ */
242
+ private static class PartialMatchHelper {
243
+
244
+ private final List <PartialMatch > partialMatches = new ArrayList <PartialMatch >();
245
+
246
+
247
+ public PartialMatchHelper (Set <RequestMappingInfo > infos , HttpServletRequest request ) {
248
+ for (RequestMappingInfo info : infos ) {
249
+ if (info .getPatternsCondition ().getMatchingCondition (request ) != null ) {
250
+ this .partialMatches .add (new PartialMatch (info , request ));
251
+ }
272
252
}
273
253
}
274
- return result ;
275
- }
276
254
277
- private Set <MediaType > getProducibleMediaTypes (HttpServletRequest request , Set <RequestMappingInfo > partialMatches ) {
278
- Set <MediaType > result = new HashSet <MediaType >();
279
- for (RequestMappingInfo partialMatch : partialMatches ) {
280
- if (partialMatch .getProducesCondition ().getMatchingCondition (request ) == null ) {
281
- result .addAll (partialMatch .getProducesCondition ().getProducibleMediaTypes ());
255
+
256
+ /**
257
+ * Whether there any partial matches.
258
+ */
259
+ public boolean isEmpty () {
260
+ return this .partialMatches .isEmpty ();
261
+ }
262
+
263
+ /**
264
+ * Any partial matches for "methods"?
265
+ */
266
+ public boolean hasMethodsMismatch () {
267
+ for (PartialMatch match : this .partialMatches ) {
268
+ if (match .hasMethodsMatch ()) {
269
+ return false ;
270
+ }
282
271
}
272
+ return true ;
283
273
}
284
- return result ;
285
- }
286
274
287
- private List <String []> getRequestParams (HttpServletRequest request , Set <RequestMappingInfo > partialMatches ) {
288
- List <String []> result = new ArrayList <String []>();
289
- for (RequestMappingInfo partialMatch : partialMatches ) {
290
- ParamsRequestCondition condition = partialMatch .getParamsCondition ();
291
- Set <NameValueExpression <String >> expressions = condition .getExpressions ();
292
- if (!CollectionUtils .isEmpty (expressions ) && condition .getMatchingCondition (request ) == null ) {
293
- int i = 0 ;
294
- String [] array = new String [expressions .size ()];
295
- for (NameValueExpression <String > expression : expressions ) {
296
- array [i ++] = expression .toString ();
275
+ /**
276
+ * Any partial matches for "methods" and "consumes"?
277
+ */
278
+ public boolean hasConsumesMismatch () {
279
+ for (PartialMatch match : this .partialMatches ) {
280
+ if (match .hasConsumesMatch ()) {
281
+ return false ;
297
282
}
298
- result .add (array );
299
283
}
284
+ return true ;
285
+ }
286
+
287
+ /**
288
+ * Any partial matches for "methods", "consumes", and "produces"?
289
+ */
290
+ public boolean hasProducesMismatch () {
291
+ for (PartialMatch match : this .partialMatches ) {
292
+ if (match .hasProducesMatch ()) {
293
+ return false ;
294
+ }
295
+ }
296
+ return true ;
300
297
}
301
- return result ;
302
- }
303
298
299
+ /**
300
+ * Any partial matches for "methods", "consumes", "produces", and "params"?
301
+ */
302
+ public boolean hasParamsMismatch () {
303
+ for (PartialMatch match : this .partialMatches ) {
304
+ if (match .hasParamsMatch ()) {
305
+ return false ;
306
+ }
307
+ }
308
+ return true ;
309
+ }
310
+
311
+ /**
312
+ * Return declared HTTP methods.
313
+ */
314
+ public Set <String > getAllowedMethods () {
315
+ Set <String > result = new LinkedHashSet <String >();
316
+ for (PartialMatch match : this .partialMatches ) {
317
+ for (RequestMethod method : match .getInfo ().getMethodsCondition ().getMethods ()) {
318
+ result .add (method .name ());
319
+ }
320
+ }
321
+ return result ;
322
+ }
323
+
324
+ /**
325
+ * Return declared "consumable" types but only among those that also
326
+ * match the "methods" condition.
327
+ */
328
+ public Set <MediaType > getConsumableMediaTypes () {
329
+ Set <MediaType > result = new LinkedHashSet <MediaType >();
330
+ for (PartialMatch match : this .partialMatches ) {
331
+ if (match .hasMethodsMatch ()) {
332
+ result .addAll (match .getInfo ().getConsumesCondition ().getConsumableMediaTypes ());
333
+ }
334
+ }
335
+ return result ;
336
+ }
337
+
338
+ /**
339
+ * Return declared "producible" types but only among those that also
340
+ * match the "methods" and "consumes" conditions.
341
+ */
342
+ public Set <MediaType > getProducibleMediaTypes () {
343
+ Set <MediaType > result = new LinkedHashSet <MediaType >();
344
+ for (PartialMatch match : this .partialMatches ) {
345
+ if (match .hasConsumesMatch ()) {
346
+ result .addAll (match .getInfo ().getProducesCondition ().getProducibleMediaTypes ());
347
+ }
348
+ }
349
+ return result ;
350
+ }
351
+
352
+ /**
353
+ * Return declared "params" conditions but only among those that also
354
+ * match the "methods", "consumes", and "params" conditions.
355
+ */
356
+ public List <String []> getParamConditions () {
357
+ List <String []> result = new ArrayList <String []>();
358
+ for (PartialMatch match : this .partialMatches ) {
359
+ if (match .hasProducesMatch ()) {
360
+ Set <NameValueExpression <String >> set = match .getInfo ().getParamsCondition ().getExpressions ();
361
+ if (!CollectionUtils .isEmpty (set )) {
362
+ int i = 0 ;
363
+ String [] array = new String [set .size ()];
364
+ for (NameValueExpression <String > expression : set ) {
365
+ array [i ++] = expression .toString ();
366
+ }
367
+ result .add (array );
368
+ }
369
+ }
370
+ }
371
+ return result ;
372
+ }
373
+
374
+
375
+ /**
376
+ * Container for a RequestMappingInfo that matches the URL path at least.
377
+ */
378
+ private static class PartialMatch {
379
+
380
+ private final RequestMappingInfo info ;
381
+
382
+ private final boolean methodsMatch ;
383
+
384
+ private final boolean consumesMatch ;
385
+
386
+ private final boolean producesMatch ;
387
+
388
+ private final boolean paramsMatch ;
389
+
390
+
391
+ /**
392
+ * @param info RequestMappingInfo that matches the URL path.
393
+ * @param request the current request
394
+ */
395
+ public PartialMatch (RequestMappingInfo info , HttpServletRequest request ) {
396
+ this .info = info ;
397
+ this .methodsMatch = info .getMethodsCondition ().getMatchingCondition (request ) != null ;
398
+ this .consumesMatch = info .getConsumesCondition ().getMatchingCondition (request ) != null ;
399
+ this .producesMatch = info .getProducesCondition ().getMatchingCondition (request ) != null ;
400
+ this .paramsMatch = info .getParamsCondition ().getMatchingCondition (request ) != null ;
401
+ }
402
+
403
+
404
+ public RequestMappingInfo getInfo () {
405
+ return this .info ;
406
+ }
407
+
408
+ public boolean hasMethodsMatch () {
409
+ return this .methodsMatch ;
410
+ }
411
+
412
+ public boolean hasConsumesMatch () {
413
+ return hasMethodsMatch () && this .consumesMatch ;
414
+ }
415
+
416
+ public boolean hasProducesMatch () {
417
+ return hasConsumesMatch () && this .producesMatch ;
418
+ }
419
+
420
+ public boolean hasParamsMatch () {
421
+ return hasProducesMatch () && this .paramsMatch ;
422
+ }
423
+
424
+ @ Override
425
+ public String toString () {
426
+ return this .info .toString ();
427
+ }
428
+ }
429
+ }
304
430
305
431
/**
306
432
* Default handler for HTTP OPTIONS.
0 commit comments