<p dir="auto"><strong><a href="https://jira.spring.io/secure/ViewProfile.jspa?name=ari1974" rel="nofollow">Ari Miller</a></strong> opened <strong><a href="https://jira.spring.io/browse/SPR-4551?redirect=false" rel="nofollow">SPR-4551</a></strong> and commented</p>
<p dir="auto">My description of the problem below uses the 2.5.2 version of the classes described.  Note the problem also occurs in 2.5.1.</p>
<p dir="auto">We believe we have discovered an issue with the RequestMapping method resolution logic, that can cause particular requests to be handled by the wrong RequestMapping method based on the order in which the controller methods are returned by Class.getDeclaredMethods() (which is not deterministic).</p>
<p dir="auto">The AnnotationMethodHandlerAdapter.ServletHandlerMethodResolver.resolveHandlerMethod will return the appropriate method to invoke for a given request.  It starts with a series of methods that might potentially be used to handle the request, based on the <code class="notranslate">@RequestMapping</code> annotating those methods in the target handler.  The problems stems from the dependency on initial order in the logic used to select from multiple potential methods:</p>
<p dir="auto">Lines 452 - 472 of AnnotationMethodHandlerAdapter:<br>
[CODE]<br>
else if (!targetHandlerMethods.isEmpty()) {<br>
RequestMappingInfo bestMappingMatch = null;<br>
String bestPathMatch = null;<br>
for (RequestMappingInfo mapping : targetHandlerMethods.keySet()) {<br>
String mappedPath = targetPathMatches.get(mapping);<br>
if (bestMappingMatch == null) {<br>
bestMappingMatch = mapping;<br>
bestPathMatch = mappedPath;<br>
}<br>
else {<br>
if ((mappedPath != null &amp;&amp; (bestPathMatch == null ||<br>
mappedPath.equals(lookupPath) || bestPathMatch.length() &lt; mappedPath.length())) ||<br>
(bestMappingMatch.methods.length == 0 &amp;&amp; mapping.methods.length &gt; 0) ||<br>
bestMappingMatch.params.length &lt; mapping.params.length) {<br>
bestMappingMatch = mapping;<br>
bestPathMatch = mappedPath;<br>
}<br>
}<br>
}<br>
return targetHandlerMethods.get(bestMappingMatch);<br>
}<br>
[/CODE]<br>
Note that the if((mappedPath != null ... )  logic gives multiple possible opportunities for a method to be deemed a betterMappingMatch than the current best match.  Given certain requestMapping annotations, this results in whichever method is last in the LinkedHashMap targetHandlerMethods being returned, because the last method will meet one of the || conditions in that if statement.</p>
<p dir="auto">Here is a specific example, with two methods on a single controller that could handle an incoming request with the path /enterAccessCode.do:</p>
<p dir="auto"><code class="notranslate">@RequestMapping</code>("/**/enterAccessCode.do")<br>
public ModelAndView methodWithPathMapping      -- this is the method we want to handle the request</p>
<p dir="auto"><code class="notranslate">@RequestMapping</code>(method = {RequestMethod.GET, RequestMethod.POST})<br>
public ModelAndView methodWithMethodMapping()</p>
<p dir="auto">Say the LinkedHashMap targetHandlerMethods has the following order:</p>
<ol dir="auto">
<li>methodWithPathMapping</li>
<li>methodWithMethodMapping</li>
</ol>
<p dir="auto">Initially, the bestMappingMatch starts with methodWithPathMapping.<br>
When it gets to the if statement, methodWithMethodMapping becomes the bestMappingMatch, because this part of the statement is true:<br>
(bestMappingMatch.methods.length == 0 &amp;&amp; mapping.methods.length &gt; 0)<br>
This is not the desired behavior.</p>
<p dir="auto">If you have the reverse order:</p>
<ol dir="auto">
<li>methodWithMethodMapping</li>
<li>methodWithPathMapping</li>
</ol>
<p dir="auto">methodWithMethodMapping starts out as the bestMappingMatch, but know the first part of the if statement is true, so methodWithPathMapping becomes the best match.</p>
<p dir="auto">As to this not being deterministic:<br>
HandlerMethodResolver.handlerMethods is a LinkedHashSet created based on ReflectionUtils.doWithMethods, which in turn depends on the<br>
result of targetClass.getDeclaredMethods() (Javadoc declares:  The elements in the array returned are not sorted and are not in any particular order).   HandlerMethodResolver.handlerMethods is iterated through to create targetHandlerMethods, which is once again then not in a deterministic order.  We've seen this result in different behavior depending on the JVM (and I think hardware) -- some hardware consistently uses the appropriate method to handle the incoming request, some hardware, because of the different method order, uses the inappropriate methodWithMethodMapping.<br>
Our workaround for this is to avoid having our general handler use method = {RequestMethod.GET, RequestMethod.POST} for the methodWithMethodMapping -- this makes all of the if statement || blocks false.<br>
My claim is that the logic to determine the best mapping match in the if block should be insensitive to initial order when finding the bestMappingMatch.</p>
<p dir="auto">Here is a crude and untested way to accomplish that:<br>
Replace:<br>
[CODE]<br>
if ((mappedPath != null &amp;&amp; (bestPathMatch == null ||<br>
mappedPath.equals(lookupPath) || bestPathMatch.length() &lt; mappedPath.length())) ||<br>
(bestMappingMatch.methods.length == 0 &amp;&amp; mapping.methods.length &gt; 0) ||<br>
bestMappingMatch.params.length &lt; mapping.params.length) {<br>
bestMappingMatch = mapping;<br>
bestPathMatch = mappedPath;<br>
}<br>
[/CODE]<br>
with</p>
<p dir="auto">[CODE]<br>
private boolean isBetterPathMatch(String mappedPath, String mappedPathToCompare, String lookupPath) {<br>
return (mappedPath != null &amp;&amp; (mappedPathToCompare == null ||<br>
mappedPath.equals(lookupPath) || mappedPathToCompare.length() &lt; mappedPath.length()));<br>
}</p>
<p dir="auto">private boolean isBetterMethodMatch(RequestMappingInfo mapping, RequestMappingInfo mappingToCompare) {<br>
return mappingToCompare.methods.length == 0 &amp;&amp; mapping.methods.length &gt; 0;<br>
}</p>
<p dir="auto">private boolean isBetterParamMatch(RequestMappingInfo mapping, RequestMappingInfo mappingToCompare) {<br>
return mappingToCompare.params.length &lt; mapping.params.length;<br>
}</p>
<p dir="auto">if (isBetterPathMatch(mapppedPath, bestPathMatch, lookupPath) ||<br>
(! isBetterPathMatch(bestPathMatch, mappedPath, lookupPath) &amp;&amp; isBetterMethodMatch(mapping, bestMappingMatch)) ||<br>
(! isBetterPathMatch(bestPathMatch, mappedPath, lookupPath) &amp;&amp; ! isBetterMethodMatch(bestMappingMatch, mapping) &amp;&amp; isBetterParamMatch(mapping, bestMappingMatch)) {<br>
bestMappingMatch = mapping;<br>
bestPathMatch = mappedPath;<br>
}<br>
[/CODE]</p>
<hr>
<p dir="auto"><strong>Affects:</strong> 2.5.1, 2.5.2</p>
<p dir="auto"><strong>Attachments:</strong></p>
<ul dir="auto">
<li><a href="https://jira.spring.io/secure/attachment/13743/4551Fix.diff" rel="nofollow">4551Fix.diff</a> (<em>2.63 kB</em>)</li>
<li><a href="https://jira.spring.io/secure/attachment/13745/AnnotationMethodHandlerAdapter.java" rel="nofollow">AnnotationMethodHandlerAdapter.java</a> (<em>29.97 kB</em>)</li>
<li><a href="https://jira.spring.io/secure/attachment/13744/SpringAnnotationMethodHandlerAdapterNotDeterministicTest.java" rel="nofollow">SpringAnnotationMethodHandlerAdapterNotDeterministicTest.java</a> (<em>3.74 kB</em>)</li>
</ul>
<p dir="auto"><strong>Issue Links:</strong></p>
<ul dir="auto">
<li><a class="issue-link js-issue-link" data-error-text="Failed to load title" data-id="398090255" data-permission-text="Title is private" data-url="https://github.com/spring-projects/spring-framework/issues/9744" data-hovercard-type="issue" data-hovercard-url="/spring-projects/spring-framework/issues/9744/hovercard" href="https://github.com/spring-projects/spring-framework/issues/9744">#9744</a> <code class="notranslate">@RequestMapping</code> method resolution issue with duplicate paths</li>
</ul>
<p dir="auto"><strong><a href="https://jira.spring.io/secure/ViewProfile.jspa?name=gupabhi" rel="nofollow">Abhishek Gupta</a></strong> opened <strong><a href="https://jira.spring.io/browse/SPR-4873?redirect=false" rel="nofollow">SPR-4873</a></strong> and commented</p>
<p dir="auto">Sub: Additional sql-state classification required for  Sybase and DB2</p>
<p dir="auto">We currently classify SQLExceptions as Transient or Non-Transient based on SQL-States we provide in a configuration file.<br>
Following are the SQL-States for DB2 and Sybase that we use:</p>
<p dir="auto">SQLStates for DB2,DB2UDB:</p>
<ul dir="auto">
<li>08001     Transient Exception           No Connection  (application requester could not connect to the target.)</li>
<li>40003    Transient Exception            No Connection</li>
<li>S1000    Transient Exception             Communication Failure</li>
<li>57011     Transient Exception             Out of virtual memory</li>
<li>57019     Transient Exception             DB currently in use</li>
<li>40001     Transient Exception             Deadlock detected</li>
<li>23505     Non-Transient Exception    Duplicate Key</li>
</ul>
<p dir="auto">SQLStates for Sybase:</p>
<ul dir="auto">
<li>JZ006     Transient Exception            Unexpected I/O error</li>
<li>JW0I0     Transient Exception            Internal error with a timed I/O stream</li>
<li>JZ0I1      Transient Exception            I/O timeout</li>
<li>JZ0I2      Transient Exception            I/O timeout</li>
<li>JZ0C0    Transient Exception            Connecton already closed</li>
<li>JZ0P1    Transient Exception            Unexptected result type</li>
<li>JZ0EM   Transient Exception             End of Data</li>
<li>JZ0P4    Transient Exception             Protocol Error</li>
<li>40001    Transient Exception            Deadlock detected</li>
<li>23000     Non-Transient                    Data Integrity Violated</li>
</ul>
<p dir="auto">The descriptions for the above sybase sql-states can be found here: <a href="http://manuals.sybase.com/onlinebooks/group-jcarc/jcg0400e/jcrg/%60@Generic__BookTextView%60/7402" rel="nofollow">http://manuals.sybase.com/onlinebooks/group-jcarc/jcg0400e/jcrg/`@Generic__BookTextView`/7402</a></p>
<p dir="auto">Not all of the above given error-states are supported in spring's SQLExceptionTranslator. It would be nice to have these available out of the box in spring, so we can completely replace our sql-state based error-handling with that of spring's.</p>
<hr>
<p dir="auto"><strong>Affects:</strong> 2.5.2, 2.5.3, 2.5.4</p>
<p dir="auto"><strong>Issue Links:</strong></p>
<ul dir="auto">
<li><a class="issue-link js-issue-link" data-error-text="Failed to load title" data-id="398089009" data-permission-text="Title is private" data-url="https://github.com/spring-projects/spring-framework/issues/9575" data-hovercard-type="issue" data-hovercard-url="/spring-projects/spring-framework/issues/9575/hovercard" href="https://github.com/spring-projects/spring-framework/issues/9575">#9575</a> Adding hook into SQL State based exception translation</li>
</ul>