Skip to content
This repository was archived by the owner on Mar 10, 2025. It is now read-only.

Commit 279d70a

Browse files
authored
Merge pull request #118 from codeconsole/scaffold-annotations
@scaffold annotation for Controllers and Services
2 parents d11109e + 19b8a12 commit 279d70a

File tree

6 files changed

+234
-12
lines changed

6 files changed

+234
-12
lines changed
Lines changed: 59 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,59 @@
1+
package grails.plugin.scaffolding
2+
3+
import grails.artefact.Artefact
4+
import grails.gorm.api.GormAllOperations
5+
import grails.gorm.transactions.ReadOnly
6+
import grails.gorm.transactions.Transactional
7+
import grails.util.GrailsNameUtils
8+
import groovy.transform.CompileStatic
9+
import org.grails.datastore.gorm.GormEntity
10+
11+
@Artefact("Service")
12+
@ReadOnly
13+
@CompileStatic
14+
class GormService<T extends GormEntity<T>> {
15+
16+
GormAllOperations<T> resource
17+
String resourceName
18+
String resourceClassName
19+
boolean readOnly
20+
21+
GormService(Class<T> resource, boolean readOnly) {
22+
this.resource = resource.getDeclaredConstructor().newInstance() as GormAllOperations<T>
23+
this.readOnly = readOnly
24+
resourceClassName = resource.simpleName
25+
resourceName = GrailsNameUtils.getPropertyName(resource)
26+
}
27+
28+
protected T queryForResource(Serializable id) {
29+
resource.get(id)
30+
}
31+
32+
T get(Serializable id) {
33+
queryForResource(id)
34+
}
35+
36+
List<T> list(Map args) {
37+
resource.list(args)
38+
}
39+
40+
Long count() {
41+
resource.count()
42+
}
43+
44+
@Transactional
45+
void delete(Serializable id) {
46+
if (readOnly) {
47+
return
48+
}
49+
queryForResource(id).delete flush: true
50+
}
51+
52+
@Transactional
53+
T save(T instance) {
54+
if (readOnly) {
55+
return instance
56+
}
57+
instance.save flush: true
58+
}
59+
}
Lines changed: 43 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,43 @@
1+
package grails.plugin.scaffolding
2+
3+
import grails.artefact.Artefact
4+
import grails.gorm.transactions.ReadOnly
5+
import grails.rest.RestfulController
6+
import grails.util.Holders
7+
8+
@Artefact("Controller")
9+
@ReadOnly
10+
class RestfulServiceController<T> extends RestfulController<T> {
11+
12+
RestfulServiceController(Class<T> resource, boolean readOnly) {
13+
super(resource, readOnly)
14+
}
15+
16+
protected def getService() {
17+
Holders.grailsApplication.getMainContext().getBean(resourceName + 'Service')
18+
}
19+
20+
protected T queryForResource(Serializable id) {
21+
getService().get(id)
22+
}
23+
24+
protected List<T> listAllResources(Map params) {
25+
getService().list(params)
26+
}
27+
28+
protected Integer countResources() {
29+
getService().count()
30+
}
31+
32+
protected T saveResource(T resource) {
33+
getService().save(resource)
34+
}
35+
36+
protected T updateResource(T resource) {
37+
getService().save(resource)
38+
}
39+
40+
protected void deleteResource(T resource) {
41+
getService().delete(resource)
42+
}
43+
}

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

Lines changed: 9 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -2,6 +2,7 @@ package grails.plugin.scaffolding
22

33
import grails.codegen.model.ModelBuilder
44
import grails.io.IOUtils
5+
import grails.plugin.scaffolding.annotation.Scaffold
56
import grails.util.BuildSettings
67
import grails.util.Environment
78
import groovy.text.GStringTemplateEngine
@@ -82,6 +83,14 @@ class ScaffoldingViewResolver extends GroovyPageViewResolver implements Resource
8283
def controllerClass = webR.controllerClass
8384

8485
def scaffoldValue = controllerClass?.getPropertyValue("scaffold")
86+
if (!scaffoldValue) {
87+
Scaffold scaffoldAnnotation = controllerClass?.clazz?.getAnnotation(Scaffold)
88+
scaffoldValue = scaffoldAnnotation?.domain()
89+
if (scaffoldValue == Void) {
90+
scaffoldValue = null
91+
}
92+
}
93+
8594
if (scaffoldValue instanceof Class) {
8695
def shortViewName = viewName.substring(viewName.lastIndexOf('/') + 1)
8796
Resource res = null
Lines changed: 14 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,14 @@
1+
package grails.plugin.scaffolding.annotation;
2+
3+
import java.lang.annotation.ElementType;
4+
import java.lang.annotation.Retention;
5+
import java.lang.annotation.RetentionPolicy;
6+
import java.lang.annotation.Target;
7+
8+
@Retention(RetentionPolicy.RUNTIME)
9+
@Target(ElementType.TYPE)
10+
public @interface Scaffold {
11+
Class<?> value() default Void.class;
12+
Class<?> domain() default Void.class;
13+
boolean readOnly() default false;
14+
}

src/main/groovy/org/grails/compiler/scaffolding/ScaffoldingControllerInjector.groovy

Lines changed: 36 additions & 12 deletions
Original file line numberDiff line numberDiff line change
@@ -2,19 +2,22 @@ package org.grails.compiler.scaffolding
22

33
import grails.compiler.ast.AstTransformer
44
import grails.compiler.ast.GrailsArtefactClassInjector
5+
import grails.plugin.scaffolding.annotation.Scaffold
56
import grails.rest.RestfulController
67
import groovy.transform.CompileStatic
78
import org.codehaus.groovy.ast.ClassHelper
89
import org.codehaus.groovy.ast.ClassNode
910
import org.codehaus.groovy.ast.expr.ClassExpression
11+
import org.codehaus.groovy.ast.expr.ConstantExpression
1012
import org.codehaus.groovy.classgen.GeneratorContext
1113
import org.codehaus.groovy.control.SourceUnit
1214
import org.grails.compiler.injection.GrailsASTUtils
1315
import org.grails.compiler.web.ControllerActionTransformer
1416
import org.grails.core.artefact.ControllerArtefactHandler
1517
import org.grails.plugins.web.rest.transform.ResourceTransform
18+
1619
/**
17-
* Transformation that turns a controller into a scaffolding controller at compile time of 'static scaffold = Foo'
20+
* Transformation that turns a controller into a scaffolding controller at compile time if 'static scaffold = Foo'
1821
* is specified
1922
*
2023
* @author Graeme Rocher
@@ -41,26 +44,47 @@ class ScaffoldingControllerInjector implements GrailsArtefactClassInjector {
4144
@Override
4245
void performInjectionOnAnnotatedClass(SourceUnit source, ClassNode classNode) {
4346
def propertyNode = classNode.getProperty(PROPERTY_SCAFFOLD)
47+
def annotationNode = classNode.getAnnotations(ClassHelper.make(Scaffold)).find()
4448

4549
def expression = propertyNode?.getInitialExpression()
46-
if(expression instanceof ClassExpression) {
47-
48-
ClassNode superClassNode = ClassHelper.make(RestfulController).getPlainNodeReference()
49-
def currentSuperClass = classNode.getSuperClass()
50-
if(currentSuperClass.equals( GrailsASTUtils.OBJECT_CLASS_NODE )) {
51-
def domainClass = ((ClassExpression) expression).getType()
50+
if (expression instanceof ClassExpression || annotationNode) {
51+
ClassNode controllerClassNode = annotationNode?.getMember("value")?.type
52+
ClassNode superClassNode = ClassHelper.make(controllerClassNode?.getTypeClass()?:RestfulController).getPlainNodeReference()
53+
ClassNode currentSuperClass = classNode.getSuperClass()
54+
if (currentSuperClass.equals(GrailsASTUtils.OBJECT_CLASS_NODE)) {
55+
def domainClass = expression? ((ClassExpression) expression).getType() : null
56+
if (!domainClass) {
57+
domainClass = annotationNode.getMember("domain")?.type
58+
if (!domainClass) {
59+
domainClass = extractGenericDomainClass(controllerClassNode)
60+
if (domainClass) {
61+
// set the domain value on the annotation so that ScaffoldingViewResolver can identify the domain object.
62+
annotationNode.addMember("domain", new ClassExpression(domainClass))
63+
}
64+
}
65+
if (!domainClass) {
66+
GrailsASTUtils.error(source, classNode, "Scaffolded controller (${classNode.name}) with @Scaffold does not have domain class set.", true)
67+
}
68+
}
5269
classNode.setSuperClass(GrailsASTUtils.nonGeneric(superClassNode, domainClass))
53-
new ResourceTransform().addConstructor(classNode, domainClass, false)
54-
}
55-
else if( ! currentSuperClass.isDerivedFrom(superClassNode)) {
70+
def readOnlyExpression = (ConstantExpression) annotationNode.getMember("readOnly")
71+
new ResourceTransform().addConstructor(classNode, domainClass, readOnlyExpression?.getValue()?.asBoolean()?:false)
72+
} else if (!currentSuperClass.isDerivedFrom(superClassNode)) {
5673
GrailsASTUtils.error(source, classNode, "Scaffolded controllers (${classNode.name}) cannot extend other classes: ${currentSuperClass.getName()}", true)
5774
}
58-
}
59-
else if(propertyNode != null) {
75+
} else if (propertyNode != null) {
6076
GrailsASTUtils.error(source, propertyNode, "The 'scaffold' property must refer to a domain class.", true)
6177
}
6278
}
6379

80+
protected static ClassNode extractGenericDomainClass(ClassNode controllerClassNode) {
81+
def genericsTypes = controllerClassNode?.genericsTypes
82+
if (genericsTypes && genericsTypes.length > 0) {
83+
return genericsTypes[0].type
84+
}
85+
return null
86+
}
87+
6488
@Override
6589
boolean shouldInject(URL url) {
6690
return url != null && ControllerActionTransformer.CONTROLLER_PATTERN.matcher(url.getFile()).find()
Lines changed: 73 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,73 @@
1+
package org.grails.compiler.scaffolding
2+
3+
import grails.compiler.ast.AstTransformer
4+
import grails.compiler.ast.GrailsArtefactClassInjector
5+
import grails.plugin.scaffolding.GormService
6+
import grails.plugin.scaffolding.annotation.Scaffold
7+
import groovy.transform.CompileStatic
8+
import org.codehaus.groovy.ast.ClassHelper
9+
import org.codehaus.groovy.ast.ClassNode
10+
import org.codehaus.groovy.ast.expr.ConstantExpression
11+
import org.codehaus.groovy.classgen.GeneratorContext
12+
import org.codehaus.groovy.control.SourceUnit
13+
import org.grails.compiler.injection.GrailsASTUtils
14+
import org.grails.core.artefact.ServiceArtefactHandler
15+
import org.grails.io.support.GrailsResourceUtils
16+
import org.grails.plugins.web.rest.transform.ResourceTransform
17+
18+
import java.util.regex.Pattern
19+
20+
/**
21+
* Transformation that turns a service into a scaffolding service at compile time if '@ScaffoldService'
22+
* is specified
23+
*
24+
* @author Scott Murphy Heiberg
25+
* @since 5.1
26+
*/
27+
@AstTransformer
28+
@CompileStatic
29+
class ScaffoldingServiceInjector implements GrailsArtefactClassInjector {
30+
31+
final String[] artefactTypes = [ServiceArtefactHandler.TYPE] as String[]
32+
public static Pattern SERVICE_PATTERN = Pattern.compile(".+/" +
33+
GrailsResourceUtils.GRAILS_APP_DIR + "/services/(.+)Service\\.groovy");
34+
35+
@Override
36+
void performInjection(SourceUnit source, GeneratorContext context, ClassNode classNode) {
37+
performInjectionOnAnnotatedClass(source, classNode)
38+
}
39+
40+
@Override
41+
void performInjection(SourceUnit source, ClassNode classNode) {
42+
performInjectionOnAnnotatedClass(source, classNode)
43+
}
44+
45+
@Override
46+
void performInjectionOnAnnotatedClass(SourceUnit source, ClassNode classNode) {
47+
def annotationNode = classNode.getAnnotations(ClassHelper.make(Scaffold)).find()
48+
if (annotationNode) {
49+
ClassNode serviceClassNode = annotationNode?.getMember("value")?.type
50+
ClassNode superClassNode = ClassHelper.make(serviceClassNode?.getTypeClass()?:GormService).getPlainNodeReference()
51+
ClassNode currentSuperClass = classNode.getSuperClass()
52+
if (currentSuperClass.equals(GrailsASTUtils.OBJECT_CLASS_NODE)) {
53+
def domainClass = annotationNode.getMember("domain")?.type
54+
if (!domainClass) {
55+
domainClass = ScaffoldingControllerInjector.extractGenericDomainClass(serviceClassNode)
56+
}
57+
if (!domainClass) {
58+
GrailsASTUtils.error(source, classNode, "Scaffolded service (${classNode.name}) with @Scaffold does not have domain class set.", true)
59+
}
60+
classNode.setSuperClass(GrailsASTUtils.nonGeneric(superClassNode, domainClass))
61+
def readOnlyExpression = (ConstantExpression) annotationNode.getMember("readOnly")
62+
new ResourceTransform().addConstructor(classNode, domainClass, readOnlyExpression?.getValue()?.asBoolean()?:false)
63+
} else if (!currentSuperClass.isDerivedFrom(superClassNode)) {
64+
GrailsASTUtils.error(source, classNode, "Scaffolded services (${classNode.name}) cannot extend other classes: ${currentSuperClass.getName()}", true)
65+
}
66+
}
67+
}
68+
69+
@Override
70+
boolean shouldInject(URL url) {
71+
return url != null && SERVICE_PATTERN.matcher(url.getFile()).find()
72+
}
73+
}

0 commit comments

Comments
 (0)