@@ -34,6 +34,7 @@ import org.springframework.core.io.UrlResource
3434import org.springframework.web.servlet.View
3535
3636import grails.codegen.model.ModelBuilder
37+ import grails.core.GrailsControllerClass
3738import grails.io.IOUtils
3839import grails.plugin.scaffolding.annotation.Scaffold
3940import grails.util.BuildSettings
@@ -86,11 +87,18 @@ class ScaffoldingViewResolver extends GroovyPageViewResolver implements Resource
8687
8788 ResourceLoader resourceLoader
8889 protected Map<String , View > generatedViewCache = new ConcurrentHashMap<> ()
90+ protected Map<Class , Object > scaffoldValueCache = new ConcurrentHashMap<> ()
8991 protected boolean enableReload = false
92+ protected boolean enableNamespaceViewDefaults = false
93+
9094 void setEnableReload (boolean enableReload ) {
9195 this . enableReload = enableReload
9296 }
9397
98+ void setEnableNamespaceViewDefaults (boolean enableNamespaceViewDefaults ) {
99+ this . enableNamespaceViewDefaults = enableNamespaceViewDefaults
100+ }
101+
94102 protected String buildCacheKey (String viewName ) {
95103 String viewCacheKey = groovyPageLocator. resolveViewFormat(viewName)
96104 String currentControllerKeyPrefix = resolveCurrentControllerKeyPrefixes(viewName. startsWith(' /' ))
@@ -128,53 +136,119 @@ class ScaffoldingViewResolver extends GroovyPageViewResolver implements Resource
128136 @Override
129137 protected View loadView (String viewName , Locale locale ) throws Exception {
130138 def view = super . loadView(viewName, locale)
131- if (view == null ) {
132- String cacheKey = buildCacheKey(viewName)
133- view = enableReload ? null : generatedViewCache. get(cacheKey)
134- if (view != null ) {
139+
140+ if (view != null ) {
141+ if (! enableNamespaceViewDefaults) {
135142 return view
136- } else {
137- def webR = GrailsWebRequest . lookup()
138- def controllerClass = webR. controllerClass
139-
140- def scaffoldValue = controllerClass?. getPropertyValue(' scaffold' )
141- if (! scaffoldValue) {
142- Scaffold scaffoldAnnotation = controllerClass?. clazz?. getAnnotation(Scaffold )
143- scaffoldValue = scaffoldAnnotation?. domain()
144- if (scaffoldValue == Void ) {
145- scaffoldValue = null
146- }
147- }
143+ }
148144
149- if (scaffoldValue instanceof Class ) {
150- def shortViewName = viewName. substring(viewName. lastIndexOf(' /' ) + 1 )
151- Resource res = controllerClass. namespace ? resolveResource(controllerClass. clazz, " ${ controllerClass.namespace} /${ shortViewName} " ) : null
152- if (! res?. exists()) {
153- res = resolveResource(controllerClass. clazz, shortViewName)
154- }
155- if (res. exists()) {
156- def model = model((Class ) scaffoldValue)
157- def viewGenerator = new GStringTemplateEngine ()
158- Template t = viewGenerator. createTemplate(res.URL )
159-
160- def contents = new FastStringWriter ()
161- t. make(model. asMap()). writeTo(contents)
162-
163- def template = templateEngine. createTemplate(new ByteArrayResource (contents. toString(). getBytes(templateEngine. gspEncoding), " view:$cacheKey " ), ! enableReload)
164- view = new GroovyPageView ()
165- view. setServletContext(getServletContext())
166- view. setTemplate(template)
167- view. setApplicationContext(getApplicationContext())
168- view. setTemplateEngine(templateEngine)
169- view. afterPropertiesSet()
170- generatedViewCache. put(cacheKey, view)
171- return view
172- } else {
173- return view
174- }
145+ def controllerClass = GrailsWebRequest . lookup()?. controllerClass
146+ if (controllerClass?. namespace) {
147+ // Check if the view found is already a namespace-specific view
148+ def isNamespaceSpecificView = view instanceof GroovyPageView &&
149+ view. url?. contains(" /${ controllerClass.namespace} /" )
150+
151+ if (! isNamespaceSpecificView) {
152+ // View is a fallback (non-namespaced), check for namespace-specific scaffolded template
153+ return tryGenerateScaffoldedView(viewName, controllerClass) { String shortViewName ->
154+ // Only check namespace-specific template
155+ resolveResource(controllerClass. clazz, " ${ controllerClass.namespace} /${ shortViewName} " )
156+ } ?: view
175157 }
176158 }
159+ return view
160+ }
161+
162+ def controllerClass = GrailsWebRequest . lookup()?. controllerClass
163+
164+ return tryGenerateScaffoldedView(viewName, controllerClass) { String shortViewName ->
165+ Resource res = controllerClass?. namespace ? resolveResource(controllerClass. clazz, " ${ controllerClass.namespace} /${ shortViewName} " ) : null
166+ if (! res?. exists()) {
167+ res = resolveResource(controllerClass. clazz, shortViewName)
168+ }
169+ return res
170+ }
171+ }
172+
173+ /**
174+ * Attempts to generate a scaffolded view for the given controller
175+ * @param viewName The view name
176+ * @param controllerClass The controller class
177+ * @param resourceResolver Closure that resolves the scaffold template resource given a short view name
178+ * @return The generated scaffolded view, or null if not applicable
179+ */
180+ private View tryGenerateScaffoldedView (String viewName , GrailsControllerClass controllerClass , Closure<Resource > resourceResolver ) {
181+ def scaffoldValue = getScaffoldValue(controllerClass)
182+ if (! (scaffoldValue instanceof Class )) {
183+ return null
184+ }
185+
186+ String cacheKey = buildCacheKey(viewName)
187+
188+ // Check cache first
189+ def cachedScaffoldedView = enableReload ? null : generatedViewCache. get(cacheKey)
190+ if (cachedScaffoldedView != null ) {
191+ return cachedScaffoldedView
192+ }
193+
194+ def shortViewName = viewName. substring(viewName. lastIndexOf(' /' ) + 1 )
195+ Resource scaffoldResource = resourceResolver. call(shortViewName)
196+
197+ if (scaffoldResource?. exists()) {
198+ return generateScaffoldedView(scaffoldValue, scaffoldResource, cacheKey)
199+ }
200+
201+ return null
202+ }
203+
204+ private Object getScaffoldValue (GrailsControllerClass controllerClass ) {
205+ if (! controllerClass) {
206+ return null
207+ }
208+
209+ // Cache the scaffold value to avoid repeated reflection
210+ Class controllerClazz = controllerClass. clazz
211+ if (scaffoldValueCache. containsKey(controllerClazz)) {
212+ return scaffoldValueCache. get(controllerClazz)
213+ }
214+
215+ def scaffoldValue = controllerClass. getPropertyValue(' scaffold' )
216+ if (! scaffoldValue) {
217+ Scaffold scaffoldAnnotation = controllerClazz?. getAnnotation(Scaffold )
218+ scaffoldValue = scaffoldAnnotation?. domain()
219+ if (scaffoldValue == Void ) {
220+ scaffoldValue = null
221+ }
177222 }
223+
224+ // Cache the result (even if null, to avoid repeated lookups)
225+ scaffoldValueCache. put(controllerClazz, scaffoldValue)
226+ return scaffoldValue
227+ }
228+
229+ private View generateScaffoldedView (Class scaffoldValue , Resource res , String cacheKey ) {
230+ def model = model((Class ) scaffoldValue)
231+ def viewGenerator = new GStringTemplateEngine ()
232+ Template t = viewGenerator. createTemplate(res.URL )
233+
234+ def contents = new FastStringWriter ()
235+ t. make(model. asMap()). writeTo(contents)
236+
237+ def template = templateEngine. createTemplate(new ByteArrayResource (contents. toString(). getBytes(templateEngine. gspEncoding), " view:$cacheKey " ), ! enableReload)
238+ def view = new GroovyPageView ()
239+ view. setServletContext(getServletContext())
240+ view. setTemplate(template)
241+ view. setApplicationContext(getApplicationContext())
242+ view. setTemplateEngine(templateEngine)
243+ view. afterPropertiesSet()
244+ generatedViewCache. put(cacheKey, view)
178245 return view
179246 }
247+
248+ @Override
249+ void clearCache () {
250+ super . clearCache()
251+ generatedViewCache. clear()
252+ scaffoldValueCache. clear()
253+ }
180254}
0 commit comments