Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

@Scaffold annotation for Controllers and Services #118

Merged
merged 13 commits into from
Sep 10, 2024
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
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()
}
}
Loading