codeconsole opened a new pull request, #15964:
URL: https://github.com/apache/grails-core/pull/15964

   ### Summary
   
   Since #13863 stopped auto-adding `@EnableWebMvc`, Spring Boot's 
`WebMvcAutoConfiguration` is active for Grails servlet apps and registers a 
`ContentNegotiatingViewResolver` as the highest-precedence `viewResolver`. 
While initializing, CNVR collects every `ViewResolver` bean via 
`getBeansOfType`, which force-creates the lazy `jspViewResolver`. If that 
happens before the SiteMesh 3 `BeanPostProcessor` wrap (#15585) is in effect, 
CNVR captures the **raw, non-decorating resolver** and keeps rendering HTML 
through it — GSP layouts silently stop applying. Whether the race is hit 
depends on bean-creation order, so it varies with the plugin set: some apps on 
8.0.0-M3 decorate fine, others lose all layouts.
   
   ### Diagnosis (reproduced on 8.0.0-M3)
   
   In an affected app:
   
   - `jspViewResolver` bean = `GrailsSiteMeshViewResolver@55871443` wrapping 
`ScaffoldingViewResolver@344207224` — the wrap itself worked.
   - CNVR's delegate list contained `ScaffoldingViewResolver@344207224` — **the 
same instance as the wrapper's inner**, captured pre-wrap.
   - Requests with `Accept: text/html` rendered undecorated (CNVR 
short-circuits); requests with an Accept header CNVR cannot satisfy fell 
through to the wrapped resolver and rendered **fully decorated**.
   
   ### Fix
   
   - **`Sitemesh3ViewResolverDefinitionPostProcessor`** (registered by 
`Sitemesh3AutoConfiguration`) rewrites the `jspViewResolver` bean 
**definition** into a `GrailsSiteMeshViewResolver` with the original definition 
embedded as the inner bean — mirroring the SiteMesh 2 module's 
`GrailsLayoutViewResolverPostProcessor`. The decorating resolver is what gets 
instantiated no matter how early a consumer forces the bean into existence, so 
the race is structurally impossible. It skips contexts where decoration is 
impossible (no `contentProcessor`/`decoratorSelector`, e.g. 
grails-testing-support unit-test contexts) and definitions that already 
decorate (the SiteMesh 2 layout resolver, or an existing 
`SiteMeshViewResolver`).
   - The existing bean post-processor is retained as a safety net for 
instance-registered resolvers and to keep upstream's wrap-all post-processor 
from registering; upstream never re-wraps a `SiteMeshViewResolver`, so the two 
mechanisms cannot double-decorate.
   
   ### Modernization (builds on #15934)
   
   `Sitemesh3GrailsPlugin` no longer declares `doWithSpring()` at all:
   
   - The SiteMesh property defaults moved to 
**`Sitemesh3EnvironmentPostProcessor`** (`META-INF/spring.factories`), 
contributed with lowest precedence before refresh — which also removes the 
post-hoc `PropertySourcesConfig` reassignment the plugin used to perform.
   - `grailsRenderViewMutator` is registered through **`beanRegistrar()`**, 
making this the first in-tree plugin on the modern registration API.
   
   ### Testing
   
   - New `Sitemesh3ViewResolverDefinitionPostProcessorSpec` (9 tests) including 
a regression test that simulates CNVR's early `getBeansOfType(ViewResolver)` 
sweep against a registry with **no bean post-processors at all** and asserts 
the captured instance decorates.
   - New `Sitemesh3EnvironmentPostProcessorSpec` (7 tests) covering defaults, 
application-set keys winning, and the SiteMesh 2 default-layout fallback.
   - `:grails-sitemesh3:test` and `:grails-layout:test` pass.
   - Verified end-to-end against a previously affected application on 8.0.0-M3: 
with only the patched `grails-sitemesh3` jar swapped in, both a `(view:)` URL 
mapping and a controller-rendered page produce fully decorated output, 
byte-identical through the CNVR path and the fall-through path.


-- 
This is an automated message from the Apache Git Service.
To respond to the message, please log on to GitHub and use the
URL above to go to the specific comment.

To unsubscribe, e-mail: [email protected]

For queries about this service, please contact Infrastructure at:
[email protected]

Reply via email to