Skip to content

Commit

Permalink
Merge pull request #118 from codeconsole/scaffold-annotations
Browse files Browse the repository at this point in the history
@scaffold annotation for Controllers and Services
  • Loading branch information
codeconsole authored Sep 10, 2024
2 parents d11109e + 19b8a12 commit 279d70a
Show file tree
Hide file tree
Showing 6 changed files with 234 additions and 12 deletions.
59 changes: 59 additions & 0 deletions src/main/groovy/grails/plugin/scaffolding/GormService.groovy
Original file line number Diff line number Diff line change
@@ -0,0 +1,59 @@
package grails.plugin.scaffolding

import grails.artefact.Artefact
import grails.gorm.api.GormAllOperations
import grails.gorm.transactions.ReadOnly
import grails.gorm.transactions.Transactional
import grails.util.GrailsNameUtils
import groovy.transform.CompileStatic
import org.grails.datastore.gorm.GormEntity

@Artefact("Service")
@ReadOnly
@CompileStatic
class GormService<T extends GormEntity<T>> {

GormAllOperations<T> resource
String resourceName
String resourceClassName
boolean readOnly

GormService(Class<T> resource, boolean readOnly) {
this.resource = resource.getDeclaredConstructor().newInstance() as GormAllOperations<T>
this.readOnly = readOnly
resourceClassName = resource.simpleName
resourceName = GrailsNameUtils.getPropertyName(resource)
}

protected T queryForResource(Serializable id) {
resource.get(id)
}

T get(Serializable id) {
queryForResource(id)
}

List<T> list(Map args) {
resource.list(args)
}

Long count() {
resource.count()
}

@Transactional
void delete(Serializable id) {
if (readOnly) {
return
}
queryForResource(id).delete flush: true
}

@Transactional
T save(T instance) {
if (readOnly) {
return instance
}
instance.save flush: true
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,43 @@
package grails.plugin.scaffolding

import grails.artefact.Artefact
import grails.gorm.transactions.ReadOnly
import grails.rest.RestfulController
import grails.util.Holders

@Artefact("Controller")
@ReadOnly
class RestfulServiceController<T> extends RestfulController<T> {

RestfulServiceController(Class<T> resource, boolean readOnly) {
super(resource, readOnly)
}

protected def getService() {
Holders.grailsApplication.getMainContext().getBean(resourceName + 'Service')
}

protected T queryForResource(Serializable id) {
getService().get(id)
}

protected List<T> listAllResources(Map params) {
getService().list(params)
}

protected Integer countResources() {
getService().count()
}

protected T saveResource(T resource) {
getService().save(resource)
}

protected T updateResource(T resource) {
getService().save(resource)
}

protected void deleteResource(T resource) {
getService().delete(resource)
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,7 @@ package grails.plugin.scaffolding

import grails.codegen.model.ModelBuilder
import grails.io.IOUtils
import grails.plugin.scaffolding.annotation.Scaffold
import grails.util.BuildSettings
import grails.util.Environment
import groovy.text.GStringTemplateEngine
Expand Down Expand Up @@ -82,6 +83,14 @@ class ScaffoldingViewResolver extends GroovyPageViewResolver implements Resource
def controllerClass = webR.controllerClass

def scaffoldValue = controllerClass?.getPropertyValue("scaffold")
if (!scaffoldValue) {
Scaffold scaffoldAnnotation = controllerClass?.clazz?.getAnnotation(Scaffold)
scaffoldValue = scaffoldAnnotation?.domain()
if (scaffoldValue == Void) {
scaffoldValue = null
}
}

if (scaffoldValue instanceof Class) {
def shortViewName = viewName.substring(viewName.lastIndexOf('/') + 1)
Resource res = null
Expand Down
14 changes: 14 additions & 0 deletions src/main/groovy/grails/plugin/scaffolding/annotation/Scaffold.java
Original file line number Diff line number Diff line change
@@ -0,0 +1,14 @@
package grails.plugin.scaffolding.annotation;

import java.lang.annotation.ElementType;
import java.lang.annotation.Retention;
import java.lang.annotation.RetentionPolicy;
import java.lang.annotation.Target;

@Retention(RetentionPolicy.RUNTIME)
@Target(ElementType.TYPE)
public @interface Scaffold {
Class<?> value() default Void.class;
Class<?> domain() default Void.class;
boolean readOnly() default false;
}
Original file line number Diff line number Diff line change
Expand Up @@ -2,19 +2,22 @@ package org.grails.compiler.scaffolding

import grails.compiler.ast.AstTransformer
import grails.compiler.ast.GrailsArtefactClassInjector
import grails.plugin.scaffolding.annotation.Scaffold
import grails.rest.RestfulController
import groovy.transform.CompileStatic
import org.codehaus.groovy.ast.ClassHelper
import org.codehaus.groovy.ast.ClassNode
import org.codehaus.groovy.ast.expr.ClassExpression
import org.codehaus.groovy.ast.expr.ConstantExpression
import org.codehaus.groovy.classgen.GeneratorContext
import org.codehaus.groovy.control.SourceUnit
import org.grails.compiler.injection.GrailsASTUtils
import org.grails.compiler.web.ControllerActionTransformer
import org.grails.core.artefact.ControllerArtefactHandler
import org.grails.plugins.web.rest.transform.ResourceTransform

/**
* Transformation that turns a controller into a scaffolding controller at compile time of 'static scaffold = Foo'
* Transformation that turns a controller into a scaffolding controller at compile time if 'static scaffold = Foo'
* is specified
*
* @author Graeme Rocher
Expand All @@ -41,26 +44,47 @@ class ScaffoldingControllerInjector implements GrailsArtefactClassInjector {
@Override
void performInjectionOnAnnotatedClass(SourceUnit source, ClassNode classNode) {
def propertyNode = classNode.getProperty(PROPERTY_SCAFFOLD)
def annotationNode = classNode.getAnnotations(ClassHelper.make(Scaffold)).find()

def expression = propertyNode?.getInitialExpression()
if(expression instanceof ClassExpression) {

ClassNode superClassNode = ClassHelper.make(RestfulController).getPlainNodeReference()
def currentSuperClass = classNode.getSuperClass()
if(currentSuperClass.equals( GrailsASTUtils.OBJECT_CLASS_NODE )) {
def domainClass = ((ClassExpression) expression).getType()
if (expression instanceof ClassExpression || annotationNode) {
ClassNode controllerClassNode = annotationNode?.getMember("value")?.type
ClassNode superClassNode = ClassHelper.make(controllerClassNode?.getTypeClass()?:RestfulController).getPlainNodeReference()
ClassNode currentSuperClass = classNode.getSuperClass()
if (currentSuperClass.equals(GrailsASTUtils.OBJECT_CLASS_NODE)) {
def domainClass = expression? ((ClassExpression) expression).getType() : null
if (!domainClass) {
domainClass = annotationNode.getMember("domain")?.type
if (!domainClass) {
domainClass = extractGenericDomainClass(controllerClassNode)
if (domainClass) {
// set the domain value on the annotation so that ScaffoldingViewResolver can identify the domain object.
annotationNode.addMember("domain", new ClassExpression(domainClass))
}
}
if (!domainClass) {
GrailsASTUtils.error(source, classNode, "Scaffolded controller (${classNode.name}) with @Scaffold does not have domain class set.", true)
}
}
classNode.setSuperClass(GrailsASTUtils.nonGeneric(superClassNode, domainClass))
new ResourceTransform().addConstructor(classNode, domainClass, false)
}
else if( ! currentSuperClass.isDerivedFrom(superClassNode)) {
def readOnlyExpression = (ConstantExpression) annotationNode.getMember("readOnly")
new ResourceTransform().addConstructor(classNode, domainClass, readOnlyExpression?.getValue()?.asBoolean()?:false)
} else if (!currentSuperClass.isDerivedFrom(superClassNode)) {
GrailsASTUtils.error(source, classNode, "Scaffolded controllers (${classNode.name}) cannot extend other classes: ${currentSuperClass.getName()}", true)
}
}
else if(propertyNode != null) {
} else if (propertyNode != null) {
GrailsASTUtils.error(source, propertyNode, "The 'scaffold' property must refer to a domain class.", true)
}
}

protected static ClassNode extractGenericDomainClass(ClassNode controllerClassNode) {
def genericsTypes = controllerClassNode?.genericsTypes
if (genericsTypes && genericsTypes.length > 0) {
return genericsTypes[0].type
}
return null
}

@Override
boolean shouldInject(URL url) {
return url != null && ControllerActionTransformer.CONTROLLER_PATTERN.matcher(url.getFile()).find()
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,73 @@
package org.grails.compiler.scaffolding

import grails.compiler.ast.AstTransformer
import grails.compiler.ast.GrailsArtefactClassInjector
import grails.plugin.scaffolding.GormService
import grails.plugin.scaffolding.annotation.Scaffold
import groovy.transform.CompileStatic
import org.codehaus.groovy.ast.ClassHelper
import org.codehaus.groovy.ast.ClassNode
import org.codehaus.groovy.ast.expr.ConstantExpression
import org.codehaus.groovy.classgen.GeneratorContext
import org.codehaus.groovy.control.SourceUnit
import org.grails.compiler.injection.GrailsASTUtils
import org.grails.core.artefact.ServiceArtefactHandler
import org.grails.io.support.GrailsResourceUtils
import org.grails.plugins.web.rest.transform.ResourceTransform

import java.util.regex.Pattern

/**
* Transformation that turns a service into a scaffolding service at compile time if '@ScaffoldService'
* is specified
*
* @author Scott Murphy Heiberg
* @since 5.1
*/
@AstTransformer
@CompileStatic
class ScaffoldingServiceInjector implements GrailsArtefactClassInjector {

final String[] artefactTypes = [ServiceArtefactHandler.TYPE] as String[]
public static Pattern SERVICE_PATTERN = Pattern.compile(".+/" +
GrailsResourceUtils.GRAILS_APP_DIR + "/services/(.+)Service\\.groovy");

@Override
void performInjection(SourceUnit source, GeneratorContext context, ClassNode classNode) {
performInjectionOnAnnotatedClass(source, classNode)
}

@Override
void performInjection(SourceUnit source, ClassNode classNode) {
performInjectionOnAnnotatedClass(source, classNode)
}

@Override
void performInjectionOnAnnotatedClass(SourceUnit source, ClassNode classNode) {
def annotationNode = classNode.getAnnotations(ClassHelper.make(Scaffold)).find()
if (annotationNode) {
ClassNode serviceClassNode = annotationNode?.getMember("value")?.type
ClassNode superClassNode = ClassHelper.make(serviceClassNode?.getTypeClass()?:GormService).getPlainNodeReference()
ClassNode currentSuperClass = classNode.getSuperClass()
if (currentSuperClass.equals(GrailsASTUtils.OBJECT_CLASS_NODE)) {
def domainClass = annotationNode.getMember("domain")?.type
if (!domainClass) {
domainClass = ScaffoldingControllerInjector.extractGenericDomainClass(serviceClassNode)
}
if (!domainClass) {
GrailsASTUtils.error(source, classNode, "Scaffolded service (${classNode.name}) with @Scaffold does not have domain class set.", true)
}
classNode.setSuperClass(GrailsASTUtils.nonGeneric(superClassNode, domainClass))
def readOnlyExpression = (ConstantExpression) annotationNode.getMember("readOnly")
new ResourceTransform().addConstructor(classNode, domainClass, readOnlyExpression?.getValue()?.asBoolean()?:false)
} else if (!currentSuperClass.isDerivedFrom(superClassNode)) {
GrailsASTUtils.error(source, classNode, "Scaffolded services (${classNode.name}) cannot extend other classes: ${currentSuperClass.getName()}", true)
}
}
}

@Override
boolean shouldInject(URL url) {
return url != null && SERVICE_PATTERN.matcher(url.getFile()).find()
}
}

0 comments on commit 279d70a

Please sign in to comment.