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]
