<p dir="auto"><strong><a href="https://jira.spring.io/secure/ViewProfile.jspa?name=sbrannen" rel="nofollow">Sam Brannen</a></strong> opened <strong><a href="https://jira.spring.io/browse/SPR-7827?redirect=false" rel="nofollow">SPR-7827</a></strong> and commented</p>
<h4 dir="auto">Overview</h4>
<p dir="auto">Spring 3.0 already allows component stereotypes to be used in a <em>meta-annotation</em> fashion, for example by creating a custom <code class="notranslate">@TransactionalService</code> stereotype annotation which combines <code class="notranslate">@Transactional</code> and <code class="notranslate">@Service</code> in a single, reusable, application-specific annotation.</p>
<p dir="auto">As mentioned in various forums, <a href="https://jira.springsource.org/browse/SPR-7754?focusedCommentId=61545&amp;page=com.atlassian.jira.plugin.system.issuetabpanels%3Acomment-tabpanel#action_61545" rel="nofollow">a JIRA comment</a>, and discussions I've had with Spring users, it would be beneficial to provide similar <em>meta-annotation</em> support for test-related annotations.</p>
<p dir="auto">Given a custom <code class="notranslate">@TransactionalTest</code> stereotype annotation (see code listing below), multiple test classes in the application's test suite could rely on centralized configuration of the context and transaction configuration and thus avoid unnecessary duplication. See <code class="notranslate">UserRepositoryIntegrationTests</code> for an example.</p>
<h6 dir="auto">Notes on JUnit</h6>
<p dir="auto">Naturally, Spring cannot provide meta-annotation support for JUnit's <code class="notranslate">@RunWith</code> annotation, since <code class="notranslate">@RunWith</code> is processed by JUnit internals. Developers would therefore still be required to specify <code class="notranslate">SpringJUnit4ClassRunner</code> as the runner for each individual test class.</p>
<hr>
<h4 dir="auto">Deliverables</h4>
<p dir="auto">Provide meta-annotation support for the following annotations within the context of the TestContext framework.</p>
<ol class="contains-task-list">
<li class="task-list-item"><input type="checkbox" id="" disabled="" class="task-list-item-checkbox" checked=""> <code class="notranslate">@ContextConfiguration</code></li>
<li class="task-list-item"><input type="checkbox" id="" disabled="" class="task-list-item-checkbox" checked=""> <code class="notranslate">@WebAppConfiguration</code></li>
<li class="task-list-item"><input type="checkbox" id="" disabled="" class="task-list-item-checkbox" checked=""> <code class="notranslate">@ContextHierarchy</code></li>
<li class="task-list-item"><input type="checkbox" id="" disabled="" class="task-list-item-checkbox" checked=""> <code class="notranslate">@ActiveProfiles</code></li>
<li class="task-list-item"><input type="checkbox" id="" disabled="" class="task-list-item-checkbox" checked=""> <code class="notranslate">@DirtiesContext</code></li>
<li class="task-list-item"><input type="checkbox" id="" disabled="" class="task-list-item-checkbox" checked=""> <code class="notranslate">@TestExecutionListeners</code></li>
<li class="task-list-item"><input type="checkbox" id="" disabled="" class="task-list-item-checkbox" checked=""> <code class="notranslate">@IfProfileValue</code></li>
<li class="task-list-item"><input type="checkbox" id="" disabled="" class="task-list-item-checkbox" checked=""> <code class="notranslate">@ProfileValueSourceConfiguration</code></li>
<li class="task-list-item"><input type="checkbox" id="" disabled="" class="task-list-item-checkbox" checked=""> <code class="notranslate">@Transactional</code></li>
<li class="task-list-item"><input type="checkbox" id="" disabled="" class="task-list-item-checkbox" checked=""> <code class="notranslate">@BeforeTransaction</code></li>
<li class="task-list-item"><input type="checkbox" id="" disabled="" class="task-list-item-checkbox" checked=""> <code class="notranslate">@AfterTransaction</code></li>
<li class="task-list-item"><input type="checkbox" id="" disabled="" class="task-list-item-checkbox" checked=""> <code class="notranslate">@TransactionConfiguration</code></li>
<li class="task-list-item"><input type="checkbox" id="" disabled="" class="task-list-item-checkbox" checked=""> <code class="notranslate">@Rollback</code></li>
<li class="task-list-item"><input type="checkbox" id="" disabled="" class="task-list-item-checkbox" checked=""> <code class="notranslate">@Repeat</code></li>
<li class="task-list-item"><input type="checkbox" id="" disabled="" class="task-list-item-checkbox" checked=""> <code class="notranslate">@Timed</code></li>
</ol>
<hr>
<h4 dir="auto">Code Examples</h4>
<div class="snippet-clipboard-content notranslate position-relative overflow-auto" data-snippet-clipboard-copy-content="@Transactional
@ContextConfiguration({&quot;/app-config.xml&quot;, &quot;/test-data-access-config.xml&quot;})
public @interface TransactionalTest {}"><pre class="notranslate"><code class="notranslate">@Transactional
@ContextConfiguration({"/app-config.xml", "/test-data-access-config.xml"})
public @interface TransactionalTest {}
</code></pre></div>
<div class="snippet-clipboard-content notranslate position-relative overflow-auto" data-snippet-clipboard-copy-content="@TransactionalTest
@RunWith(SpringJUnit4ClassRunner.class)
public class UserRepositoryIntegrationTests { /* ... */ }"><pre class="notranslate"><code class="notranslate">@TransactionalTest
@RunWith(SpringJUnit4ClassRunner.class)
public class UserRepositoryIntegrationTests { /* ... */ }
</code></pre></div>
<hr>
<h4 dir="auto">Further Resources</h4>
<ul dir="auto">
<li>JUnit: <a href="https://github.com/junit-team/junit/issues/194/" data-hovercard-type="issue" data-hovercard-url="/junit-team/junit4/issues/194/hovercard">All JUnit annotations should be able to be applied as meta-annotations</a></li>
<li>JUnit: <a href="https://github.com/junit-team/junit/issues/202" data-hovercard-type="issue" data-hovercard-url="/junit-team/junit4/issues/202/hovercard">@RunWith(MetaAnnotations.class)</a></li>
</ul>
<hr>
<p dir="auto"><strong>Affects:</strong> 3.0.5</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="398162999" data-permission-text="Title is private" data-url="https://github.com/spring-projects/spring-framework/issues/15735" data-hovercard-type="issue" data-hovercard-url="/spring-projects/spring-framework/issues/15735/hovercard" href="https://github.com/spring-projects/spring-framework/issues/15735">#15735</a> Document meta-annotation support in the TestContext framework (<em><strong>"is depended on by"</strong></em>)</li>
<li><a class="issue-link js-issue-link" data-error-text="Failed to load title" data-id="398165753" data-permission-text="Title is private" data-url="https://github.com/spring-projects/spring-framework/issues/16081" data-hovercard-type="issue" data-hovercard-url="/spring-projects/spring-framework/issues/16081/hovercard" href="https://github.com/spring-projects/spring-framework/issues/16081">#16081</a> ContextLoader is supplied composed annotation class instead of test class for default resource detection</li>
<li><a class="issue-link js-issue-link" data-error-text="Failed to load title" data-id="398162994" data-permission-text="Title is private" data-url="https://github.com/spring-projects/spring-framework/issues/15734" data-hovercard-type="issue" data-hovercard-url="/spring-projects/spring-framework/issues/15734/hovercard" href="https://github.com/spring-projects/spring-framework/issues/15734">#15734</a> Support composed annotations declared on interfaces</li>
<li><a class="issue-link js-issue-link" data-error-text="Failed to load title" data-id="398162503" data-permission-text="Title is private" data-url="https://github.com/spring-projects/spring-framework/issues/15666" data-hovercard-type="issue" data-hovercard-url="/spring-projects/spring-framework/issues/15666/hovercard" href="https://github.com/spring-projects/spring-framework/issues/15666">#15666</a> Support meta-annotation attribute overrides in the TestContext framework</li>
</ul>
<p dir="auto"><strong>Referenced from:</strong> commits <a class="commit-link" data-hovercard-type="commit" data-hovercard-url="https://github.com/spring-projects/spring-framework/commit/5e7021f3f7775a4382cb9b9d2a3072a40c1b637b/hovercard" href="https://github.com/spring-projects/spring-framework/commit/5e7021f3f7775a4382cb9b9d2a3072a40c1b637b"><tt>5e7021f</tt></a>, <a class="commit-link" data-hovercard-type="commit" data-hovercard-url="https://github.com/spring-projects/spring-framework/commit/2bd5a535e1e6dbae83b016376ffe114dc5cb1384/hovercard" href="https://github.com/spring-projects/spring-framework/commit/2bd5a535e1e6dbae83b016376ffe114dc5cb1384"><tt>2bd5a53</tt></a>, <a class="commit-link" data-hovercard-type="commit" data-hovercard-url="https://github.com/spring-projects/spring-framework/commit/412f74f679862cd2acc2f1794adcfee2c08b0d3f/hovercard" href="https://github.com/spring-projects/spring-framework/commit/412f74f679862cd2acc2f1794adcfee2c08b0d3f"><tt>412f74f</tt></a></p>
<p dir="auto">8 votes, 11 watchers</p>
<p dir="auto"><strong><a href="https://jira.spring.io/secure/ViewProfile.jspa?name=sgarlatm" rel="nofollow">Matthew Sgarlata</a></strong> opened <strong><a href="https://jira.spring.io/browse/SPR-2233?redirect=false" rel="nofollow">SPR-2233</a></strong> and commented</p>
<p dir="auto">Let me preface this by saying I know my use case is an edge case.  However, the logging produced at WARN level by DispatcherServlet.noHandlerFound is incorrect for this case and is <strong>very</strong> confusing.  Let me just start by saying what the fix is, and then come back to how the problem can be duplicated.</p>
<p dir="auto">The current code for the method is:</p>
<div class="snippet-clipboard-content notranslate position-relative overflow-auto" data-snippet-clipboard-copy-content="protected void noHandlerFound(HttpServletRequest request, HttpServletResponse response) throws IOException {
	if (pageNotFoundLogger.isWarnEnabled()) {
		pageNotFoundLogger.warn(&quot;No mapping for [&quot; + request.getRequestURI() +
				&quot;] in DispatcherServlet with name '&quot; + getServletName() + &quot;'&quot;);
	}
	response.sendError(HttpServletResponse.SC_NOT_FOUND);
}"><pre class="notranslate"><code class="notranslate">protected void noHandlerFound(HttpServletRequest request, HttpServletResponse response) throws IOException {
	if (pageNotFoundLogger.isWarnEnabled()) {
		pageNotFoundLogger.warn("No mapping for [" + request.getRequestURI() +
				"] in DispatcherServlet with name '" + getServletName() + "'");
	}
	response.sendError(HttpServletResponse.SC_NOT_FOUND);
}
</code></pre></div>
<p dir="auto">In the case of a include done with &lt;c:import&gt;, request.getRequestURI() returns the wrong path.  UrlPathHelper.getLookupPathForRequest needs to be used to get the correct path.</p>
<p dir="auto">Here is my log output from a valid request.  In this case, in a JSP called closeDialogRefreshPane.jsp I correctly did a &lt;c:import&gt; to one of my controllers called '/dashboardsNav'</p>
<p dir="auto">DEBUG [http-80-Processor24] (DispatcherServlet:doService) - DispatcherServlet with name 'spring' received request for [/scoreboard/common/closeDialogRefreshPane.jsp]<br>
DEBUG [http-80-Processor24] (DispatcherServlet:doService) - Taking snapshot of request attributes before include<br>
DEBUG [http-80-Processor24] (DispatcherServlet:doDispatch) - Bound request context to thread: org.apache.catalina.core.ApplicationHttpRequest@1f489d9<br>
DEBUG [http-80-Processor24] (DispatcherServlet:getHandler) - Testing handler map [org.springframework.web.servlet.handler.BeanNameUrlHandlerMapping@16a346a] in DispatcherServlet with name 'spring'<br>
DEBUG [http-80-Processor24] (BeanNameUrlHandlerMapping:getHandlerInternal) - Looking up handler for [/dashboardsNav]<br>
DEBUG [http-80-Processor24] (DispatcherServlet:getHandlerAdapter) - Testing handler adapter [org.springframework.web.servlet.mvc.HttpRequestHandlerAdapter@1bd871]<br>
DEBUG [http-80-Processor24] (DispatcherServlet:getHandlerAdapter) - Testing handler adapter [org.springframework.web.servlet.mvc.SimpleControllerHandlerAdapter@1ba0609]<br>
DEBUG [http-80-Processor24] (DispatcherServlet:render) - Rendering view [org.springframework.web.servlet.view.InternalResourceView: name 'dashboardsNavView'; URL [/dashboards/dashboardsNav.jsp]] in DispatcherServlet with name 'spring'</p>
<p dir="auto">Here is my log output from an invalid request.  I accidentally tried to do a &lt;c:import&gt; from closeDialogRefreshPane.jsp to the view '/calendarsNavView' instead of to my controller called '/calendarsNav':</p>
<p dir="auto">DEBUG [http-80-Processor24] (DispatcherServlet:doService) - DispatcherServlet with name 'spring' received request for [/scoreboard/common/closeDialogRefreshPane.jsp]<br>
DEBUG [http-80-Processor24] (DispatcherServlet:doService) - Taking snapshot of request attributes before include<br>
DEBUG [http-80-Processor24] (DispatcherServlet:doDispatch) - Bound request context to thread: org.apache.catalina.core.ApplicationHttpRequest@1a34544<br>
DEBUG [http-80-Processor24] (DispatcherServlet:getHandler) - Testing handler map [org.springframework.web.servlet.handler.BeanNameUrlHandlerMapping@16a346a] in DispatcherServlet with name 'spring'<br>
DEBUG [http-80-Processor24] (BeanNameUrlHandlerMapping:getHandlerInternal) - Looking up handler for [/calendarsNavView]<br>
WARN [http-80-Processor24] (PageNotFound:noHandlerFound) - No mapping for [/scoreboard/common/closeDialogRefreshPane.jsp] in DispatcherServlet with name 'spring'</p>
<p dir="auto">As you can see, the the PageNotFound logger says /common/closeDialogRefreshPane could not be found, when in fact it was already found and dispatched to, at which point an invalid &lt;c:import&gt; call is what's actually screwing things up.</p>
<hr>
<p dir="auto"><strong>Affects:</strong> 2.0 RC1</p>