-
Notifications
You must be signed in to change notification settings - Fork 38.8k
Description
Pavel Horal opened SPR-9303 and commented
Background
Class org.springframework.web.portlet.mvc.annotation.DefaultAnnotationHandlerMapping is using PortletMappingPredicates. These predicates are used to compare priorities of different mapping (e.g. mapping with attributes has a higher precedence than the one without parameters) as they support java.lang.Comparable interface. When selecting appropriate handler AbstractMapBasedHandlerMapping#getHandlerInternal is sorting the predicates (Collections.sort).
Example Implementation
I can have handler class as follows:
@RequestMapping("view")
public class Foo {
@RenderMapping()
public String renderBar() {
// ...
}
@ActionMapping("xyz")
public void processXyz() {
// ...
}
@RenderMapping(params="page=baz")
public String renderBaz() {
// ...
}
}
If I make request with parameter page=baz, the #renderBaz() method should be invoked. This might or might not happen (explanation bellow).
Predicate Comparison
Registering the example handler above (in DefaultAnnotationHandlerMapping) will result in creating three handler mapping predicates:
RenderMappingPredicateforrenderBarwithout parametersActionMappingPredicateforprocessXyzwithout parametersRenderMappingPredicateforrenderBazwith parameters
Now what happens in AbstractMapBasedHandlerMapping#getHandlerInternal is that the list with these predicates gets sorted and the first one matching the request is picked. The problem is that when they have exactly this order they are not sorted at all.
Comparison In Detail
This is compareTo() method implementation from RenderMappingPredicate:
public int compareTo(Object other) {
if (other instanceof TypeLevelMappingPredicate) {
return 1;
}
else if (other instanceof RenderMappingPredicate) {
RenderMappingPredicate otherRender = (RenderMappingPredicate) other;
boolean hasWindowState = "".equals(this.windowState);
boolean otherHasWindowState = "".equals(otherRender.windowState);
if (hasWindowState != otherHasWindowState) {
return (hasWindowState ? -1 : 1);
}
else {
return new Integer(otherRender.params.length).compareTo(this.params.length);
}
}
return (other instanceof SpecialRequestTypePredicate ? 0 : -1);
}
The code in ActionMappingPredicate is almost identical. If you check this code against the predicates from the example you will get following comparisons:
- predicates[0].compareTo(predicates[1]) = 0 (1st RenderMappingPredicate vs ActionMappingPredicate)
- predicates[1].compareTo(predicates[2]) = 0 (ActionMappingPredicate vs 2nd RenderMappingPredicate)
The problem this issue is about is:
- predicates[0].compareTo(predicates[2]) = 1 (1st RenderMappingPredicate vs 2nd RenderMappingPredicate)
Where Is The Issue?
The issue in comparison implementation is simple - parameter length should be compared first, regardles of the predicate type (you can see that the current implementation compares parameter length only in case predicates are of the same type).
Affects: 3.1.1, 3.1.2
Attachments:
- PredicateComparisonTest.java (3.33 kB)
Issue Links:
- Wrong compareTo() implementation in Portlet mapping predicates [SPR-9605] #14239 Wrong compareTo() implementation in Portlet mapping predicates