Skip to content

Commit 8ee0f54

Browse files
committed
Allow namespace scaffold views to default to scaffold namespace templates instead of non namespace views. Fixes #15239
1 parent 06cd705 commit 8ee0f54

File tree

2 files changed

+117
-42
lines changed

2 files changed

+117
-42
lines changed

grails-scaffolding/src/main/groovy/grails/plugin/scaffolding/ScaffoldingGrailsPlugin.groovy

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -66,6 +66,7 @@ Plugin that generates scaffolded controllers and views for a Grails application.
6666
bean.lazyInit = true
6767
bean.parent = 'abstractViewResolver'
6868
enableReload = reloadEnabled
69+
enableNamespaceViewDefaults = config.getProperty('grails.scaffolding.enableNamespaceViewDefaults', Boolean, false)
6970
}
7071
}
7172
}

grails-scaffolding/src/main/groovy/grails/plugin/scaffolding/ScaffoldingViewResolver.groovy

Lines changed: 116 additions & 42 deletions
Original file line numberDiff line numberDiff line change
@@ -34,6 +34,7 @@ import org.springframework.core.io.UrlResource
3434
import org.springframework.web.servlet.View
3535

3636
import grails.codegen.model.ModelBuilder
37+
import grails.core.GrailsControllerClass
3738
import grails.io.IOUtils
3839
import grails.plugin.scaffolding.annotation.Scaffold
3940
import 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

Comments
 (0)