Skip to content

Commit a084193

Browse files
committed
Start merging grace-scaffolding into the framework
See gh-1212
2 parents cb424a0 + 151e0f3 commit a084193

File tree

107 files changed

+7360
-0
lines changed

Some content is hidden

Large Commits have some content hidden by default. Use the searchbox below for content that may be hidden.

107 files changed

+7360
-0
lines changed

grace-plugin-scaffolding/README.md

Lines changed: 27 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,27 @@
1+
# Grace Scaffolding
2+
3+
Grace Scaffolding and [Fields](https://github.com/graceframework/grace-fields) plugin work together will make you more productive.
4+
5+
Grace Scaffolding lets you generate some basic CRUD interfaces for a domain class, including:
6+
7+
* GSP views
8+
9+
* Controller actions for create/read/update/delete (CRUD) operations
10+
11+
you can set the `scaffold` property in the Controller to a specific domain class to use Dynamic scaffolding:
12+
13+
```groovy
14+
class BookController {
15+
static scaffold = Book
16+
}
17+
```
18+
19+
Grace CLI provides some useful commands to do this job quickly,
20+
21+
```bash
22+
$ grace generate-all Book
23+
$ grace generate-async-controller Book
24+
$ grace generate-controller Book
25+
$ grace generate-views Book
26+
$ grace create-scaffold-controller Book
27+
```
Lines changed: 16 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,16 @@
1+
group = "org.graceframework.plugins"
2+
3+
dependencies {
4+
api project(":grace-scaffolding-core")
5+
compileOnly "jakarta.servlet:jakarta.servlet-api"
6+
compileOnly "org.graceframework:grace-boot"
7+
api "org.graceframework:grace-core", {
8+
exclude group: 'org.graceframework', module: 'grace-datastore-core'
9+
}
10+
api "org.graceframework:grace-plugin-rest"
11+
api "org.graceframework:grace-web-gsp"
12+
compileOnly "org.graceframework:grace-cli"
13+
api "org.springframework.boot:spring-boot-autoconfigure"
14+
annotationProcessor "org.springframework.boot:spring-boot-autoconfigure-processor"
15+
annotationProcessor "org.springframework.boot:spring-boot-configuration-processor"
16+
}
Lines changed: 74 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,74 @@
1+
/*
2+
* Copyright 2024 the original author or authors.
3+
*
4+
* Licensed under the Apache License, Version 2.0 (the "License");
5+
* you may not use this file except in compliance with the License.
6+
* You may obtain a copy of the License at
7+
*
8+
* https://www.apache.org/licenses/LICENSE-2.0
9+
*
10+
* Unless required by applicable law or agreed to in writing, software
11+
* distributed under the License is distributed on an "AS IS" BASIS,
12+
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13+
* See the License for the specific language governing permissions and
14+
* limitations under the License.
15+
*/
16+
package grails.plugin.scaffolding;
17+
18+
import org.springframework.beans.factory.ObjectProvider;
19+
import org.springframework.boot.autoconfigure.AutoConfiguration;
20+
import org.springframework.boot.autoconfigure.AutoConfigureOrder;
21+
import org.springframework.boot.autoconfigure.EnableAutoConfiguration;
22+
import org.springframework.boot.autoconfigure.condition.ConditionalOnMissingBean;
23+
import org.springframework.context.annotation.Bean;
24+
import org.springframework.util.ClassUtils;
25+
26+
import grails.config.Config;
27+
import grails.config.Settings;
28+
import grails.core.GrailsApplication;
29+
import grails.util.Environment;
30+
import org.grails.gsp.GroovyPagesTemplateEngine;
31+
import org.grails.web.gsp.io.GrailsConventionGroovyPageLocator;
32+
import org.grails.web.servlet.view.GroovyPageViewResolver;
33+
import org.grails.web.util.GrailsApplicationAttributes;
34+
35+
/**
36+
* {@link EnableAutoConfiguration Auto-Configure} for Scaffolding
37+
*
38+
* @author Michael Yan
39+
* @since 6.1
40+
*/
41+
@AutoConfiguration
42+
@AutoConfigureOrder(-10)
43+
public class ScaffoldingAutoConfiguration {
44+
45+
private static final String GSP_RELOAD_INTERVAL = "grails.gsp.reload.interval";
46+
47+
@Bean(name = { "jspViewResolver", "scaffoldingViewResolver" })
48+
@ConditionalOnMissingBean
49+
public GroovyPageViewResolver scaffoldingViewResolver(ObjectProvider<GrailsApplication> grailsApplication,
50+
ObjectProvider<GrailsConventionGroovyPageLocator> groovyPageLocator,
51+
ObjectProvider<GroovyPagesTemplateEngine> groovyPagesTemplateEngine) {
52+
Config config = grailsApplication.getObject().getConfig();
53+
Environment env = Environment.getCurrent();
54+
boolean developmentMode = Environment.isDevelopmentEnvironmentAvailable();
55+
boolean gspEnableReload = config.getProperty(Settings.GSP_ENABLE_RELOAD, Boolean.class, false);
56+
boolean enableReload = env.isReloadEnabled() || gspEnableReload || (developmentMode && env == Environment.DEVELOPMENT);
57+
long gspCacheTimeout = config.getProperty(GSP_RELOAD_INTERVAL, Long.class,
58+
(developmentMode && env == Environment.DEVELOPMENT) ? 0L : 5000L);
59+
60+
boolean jstlPresent = ClassUtils.isPresent("jakarta.servlet.jsp.jstl.core.Config", getClass().getClassLoader());
61+
62+
GroovyPageViewResolver groovyPageViewResolver = new ScaffoldingViewResolver();
63+
groovyPageViewResolver.setGroovyPageLocator(groovyPageLocator.getObject());
64+
groovyPageViewResolver.setTemplateEngine(groovyPagesTemplateEngine.getObject());
65+
groovyPageViewResolver.setPrefix(GrailsApplicationAttributes.PATH_TO_VIEWS);
66+
groovyPageViewResolver.setSuffix(jstlPresent ? GroovyPageViewResolver.JSP_SUFFIX : GroovyPageViewResolver.GSP_SUFFIX);
67+
68+
if (enableReload) {
69+
groovyPageViewResolver.setCacheTimeout(gspCacheTimeout);
70+
}
71+
72+
return groovyPageViewResolver;
73+
}
74+
}
Lines changed: 35 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,35 @@
1+
package grails.plugin.scaffolding
2+
3+
import grails.plugins.*
4+
5+
class ScaffoldingGrailsPlugin extends Plugin {
6+
7+
// the version or versions of Grails the plugin is designed for
8+
def grailsVersion = "2023.0.0 > *"
9+
// resources that are excluded from plugin packaging
10+
def pluginExcludes = [
11+
"grails-app/views/error.gsp"
12+
]
13+
14+
def title = "Scaffolding Plugin" // Headline display name of the plugin
15+
def author = "Michael Yan"
16+
def authorEmail = "[email protected]"
17+
def description = '''\
18+
Plugin that generates scaffolded controllers and views for a Grace application.
19+
'''
20+
21+
// URL to the plugin's documentation
22+
def documentation = "https://github.com/graceframework/grace-scaffolding"
23+
24+
// Extra (optional) plugin metadata
25+
26+
// License: one of 'APACHE', 'GPL2', 'GPL3'
27+
def license = "APACHE"
28+
29+
// Location of the plugin's issue tracker.
30+
def issueManagement = [ system: "Github", url: "https://github.com/graceframework/grace-scaffolding/issues" ]
31+
32+
// Online location of the plugin's browseable source code.
33+
def scm = [ url: "https://github.com/graceframework/grace-scaffolding" ]
34+
35+
}
Lines changed: 102 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,102 @@
1+
package grails.plugin.scaffolding
2+
3+
import grails.codegen.model.ModelBuilder
4+
import grails.io.IOUtils
5+
import grails.util.BuildSettings
6+
import grails.util.Environment
7+
import groovy.text.GStringTemplateEngine
8+
import groovy.text.Template
9+
import groovy.transform.CompileStatic
10+
import org.grails.buffer.*
11+
import org.grails.web.servlet.mvc.GrailsWebRequest
12+
import org.grails.web.servlet.view.GroovyPageView
13+
import org.grails.web.servlet.view.GroovyPageViewResolver
14+
import org.springframework.context.ResourceLoaderAware
15+
import org.springframework.core.io.ByteArrayResource
16+
import org.springframework.core.io.FileSystemResource
17+
import org.springframework.core.io.Resource
18+
import org.springframework.core.io.ResourceLoader
19+
import org.springframework.core.io.UrlResource
20+
import org.springframework.web.servlet.View
21+
22+
import java.util.concurrent.ConcurrentHashMap
23+
24+
/**
25+
* @author Graeme Rocher
26+
* @since 3.1
27+
*/
28+
@CompileStatic
29+
class ScaffoldingViewResolver extends GroovyPageViewResolver implements ResourceLoaderAware, ModelBuilder {
30+
31+
ResourceLoader resourceLoader
32+
protected Map<String, View> generatedViewCache = new ConcurrentHashMap<>()
33+
34+
protected String buildCacheKey(String viewName) {
35+
String viewCacheKey = groovyPageLocator.resolveViewFormat(viewName)
36+
String currentControllerKeyPrefix = resolveCurrentControllerKeyPrefixes(viewName.startsWith("/"))
37+
if (currentControllerKeyPrefix != null) {
38+
viewCacheKey = currentControllerKeyPrefix + ':' + viewCacheKey
39+
}
40+
viewCacheKey
41+
}
42+
43+
@Override
44+
protected View loadView(String viewName, Locale locale) throws Exception {
45+
def view = super.loadView(viewName, locale)
46+
if(view == null) {
47+
String cacheKey = buildCacheKey(viewName)
48+
view = generatedViewCache.get(cacheKey)
49+
if(view != null) {
50+
return view
51+
}
52+
else {
53+
def webR = GrailsWebRequest.lookup()
54+
def controllerClass = webR.controllerClass
55+
56+
def scaffoldValue = controllerClass?.getPropertyValue("scaffold")
57+
if(scaffoldValue instanceof Class) {
58+
def shortViewName = viewName.substring(viewName.lastIndexOf('/') + 1)
59+
Resource res = null
60+
61+
if(Environment.isDevelopmentMode()) {
62+
res = new FileSystemResource(new File(BuildSettings.BASE_DIR, "src/main/templates/scaffolding/${shortViewName}.gsp"))
63+
}
64+
65+
if(!res?.exists()) {
66+
def url = IOUtils.findResourceRelativeToClass(controllerClass.clazz, "/templates/scaffolding/${shortViewName}.gsp")
67+
res = url ? new UrlResource(url) : null
68+
}
69+
70+
if(!res.exists()) {
71+
res = resourceLoader.getResource("classpath:META-INF/templates/scaffolding/${shortViewName}.gsp")
72+
}
73+
74+
if(res.exists()) {
75+
def model = model((Class)scaffoldValue)
76+
def viewGenerator = new GStringTemplateEngine()
77+
Template t = viewGenerator.createTemplate(res.URL)
78+
79+
def contents = new FastStringWriter()
80+
t.make(model.asMap()).writeTo(contents)
81+
82+
def template = templateEngine.createTemplate(new ByteArrayResource(contents.toString().getBytes(templateEngine.gspEncoding), "view:$cacheKey"))
83+
view = new GroovyPageView()
84+
view.setServletContext(getServletContext())
85+
view.setTemplate(template)
86+
view.setApplicationContext(getApplicationContext())
87+
view.setTemplateEngine(templateEngine)
88+
view.afterPropertiesSet()
89+
generatedViewCache.put(cacheKey, view)
90+
return view
91+
}
92+
else {
93+
return view
94+
}
95+
96+
}
97+
98+
}
99+
}
100+
return view
101+
}
102+
}
Lines changed: 130 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,130 @@
1+
/*
2+
* Copyright 2022-2024 the original author or authors.
3+
*
4+
* Licensed under the Apache License, Version 2.0 (the "License");
5+
* you may not use this file except in compliance with the License.
6+
* You may obtain a copy of the License at
7+
*
8+
* https://www.apache.org/licenses/LICENSE-2.0
9+
*
10+
* Unless required by applicable law or agreed to in writing, software
11+
* distributed under the License is distributed on an "AS IS" BASIS,
12+
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13+
* See the License for the specific language governing permissions and
14+
* limitations under the License.
15+
*/
16+
package org.grails.cli.generator
17+
18+
import grails.cli.generator.AbstractGenerator
19+
20+
/**
21+
* Scaffold generator
22+
*
23+
* @author Michael Yan
24+
* @since 6.2.0
25+
*/
26+
class ScaffoldGenerator extends AbstractGenerator {
27+
28+
private static final Map<String, String> TYPES = [
29+
'string': 'String',
30+
'String': 'String',
31+
'int': 'int',
32+
'integer': 'Integer',
33+
'Integer': 'Integer',
34+
'long': 'long',
35+
'Long': 'Long',
36+
'double': 'double',
37+
'Double': 'Double',
38+
'float': 'float',
39+
'Float': 'Float',
40+
'date': 'Date',
41+
'Date': 'Date',
42+
'boolean': 'boolean',
43+
'Boolean': 'Boolean'
44+
]
45+
46+
@Override
47+
boolean generate() {
48+
String[] args = commandLine.remainingArgs.toArray(new String[0])
49+
if (args.size() < 2) {
50+
return
51+
}
52+
boolean overwrite = commandLine.hasOption('force') || commandLine.hasOption('f')
53+
String className = args[1].capitalize()
54+
String propertyName = className.uncapitalize()
55+
56+
Map<String, String> classAttributes = new LinkedHashMap<>()
57+
String[] attributes = (args.size() >= 3 ? args[2..-1] : []) as String[]
58+
attributes.each { String item ->
59+
String[] attr = (item.contains(':') ? item.split(':') : [item, 'String']) as String[]
60+
classAttributes[attr[0]] = TYPES[attr[1]] ?: attr[1]
61+
}
62+
63+
Map<String, Object> model = new HashMap<>()
64+
model['packageName'] = defaultPackageName
65+
model['className'] = className
66+
model['modelName'] = propertyName
67+
model['propertyName'] = propertyName
68+
model['classAttributes'] = classAttributes
69+
70+
String domainClassFile = 'app/domain/' + defaultPackagePath + '/' + className + '.groovy'
71+
String domainClassSpecFile = 'src/test/groovy/' + defaultPackagePath + '/' + className + 'Spec.groovy'
72+
String controllerFile = 'app/controllers/' + defaultPackagePath + '/' + className + 'Controller.groovy'
73+
String controllerSpecFile = 'src/test/groovy/' + defaultPackagePath + '/' + className + 'ControllerSpec.groovy'
74+
String serviceFile = 'app/services/' + defaultPackagePath + '/' + className + 'Service.groovy'
75+
String serviceSpecFile = 'src/test/groovy/' + defaultPackagePath + '/' + className + 'ServiceSpec.groovy'
76+
String createGspFile = 'app/views/' + propertyName + '/' + 'create.gsp'
77+
String editGspFile = 'app/views/' + propertyName + '/' + 'edit.gsp'
78+
String indexGspFile = 'app/views/' + propertyName + '/' + 'index.gsp'
79+
String showGspFile = 'app/views/' + propertyName + '/' + 'show.gsp'
80+
81+
createFile('DomainClass.groovy.tpl', domainClassFile, model, overwrite)
82+
createFile('Controller.groovy.tpl', controllerFile, model, overwrite)
83+
createFile('Service.groovy.tpl', serviceFile, model, overwrite)
84+
createFile('create.gsp.tpl', createGspFile, model, overwrite)
85+
createFile('edit.gsp.tpl', editGspFile, model, overwrite)
86+
createFile('index.gsp.tpl', indexGspFile, model, overwrite)
87+
createFile('show.gsp.tpl', showGspFile, model, overwrite)
88+
createFile('DomainClassSpec.groovy.tpl', domainClassSpecFile, model, overwrite)
89+
createFile('ControllerSpec.groovy.tpl', controllerSpecFile, model, overwrite)
90+
createFile('ServiceSpec.groovy.tpl', serviceSpecFile, model, overwrite)
91+
92+
true
93+
}
94+
95+
@Override
96+
boolean revoke() {
97+
String[] args = commandLine.remainingArgs.toArray(new String[0])
98+
if (args.size() < 2) {
99+
return
100+
}
101+
102+
String className = args[1].capitalize()
103+
String propertyName = className.uncapitalize()
104+
105+
String domainClassFile = 'app/domain/' + defaultPackagePath + '/' + className + '.groovy'
106+
String domainClassSpecFile = 'src/test/groovy/' + defaultPackagePath + '/' + className + 'Spec.groovy'
107+
String controllerFile = 'app/controllers/' + defaultPackagePath + '/' + className + 'Controller.groovy'
108+
String controllerSpecFile = 'src/test/groovy/' + defaultPackagePath + '/' + className + 'ControllerSpec.groovy'
109+
String serviceFile = 'app/services/' + defaultPackagePath + '/' + className + 'Service.groovy'
110+
String serviceSpecFile = 'src/test/groovy/' + defaultPackagePath + '/' + className + 'ServiceSpec.groovy'
111+
String createGspFile = 'app/views/' + propertyName + '/' + 'create.gsp'
112+
String editGspFile = 'app/views/' + propertyName + '/' + 'edit.gsp'
113+
String indexGspFile = 'app/views/' + propertyName + '/' + 'index.gsp'
114+
String showGspFile = 'app/views/' + propertyName + '/' + 'show.gsp'
115+
116+
removeFile(domainClassFile)
117+
removeFile(controllerFile)
118+
removeFile(serviceFile)
119+
removeFile(createGspFile)
120+
removeFile(editGspFile)
121+
removeFile(indexGspFile)
122+
removeFile(showGspFile)
123+
removeFile(domainClassSpecFile)
124+
removeFile(controllerSpecFile)
125+
removeFile(serviceSpecFile)
126+
127+
true
128+
}
129+
130+
}

0 commit comments

Comments
 (0)