diff --git a/src/main/groovy/grails/plugin/scaffolding/GormService.groovy b/src/main/groovy/grails/plugin/scaffolding/GormService.groovy new file mode 100644 index 0000000..714c9aa --- /dev/null +++ b/src/main/groovy/grails/plugin/scaffolding/GormService.groovy @@ -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> { + + GormAllOperations resource + String resourceName + String resourceClassName + boolean readOnly + + GormService(Class resource, boolean readOnly) { + this.resource = resource.getDeclaredConstructor().newInstance() as GormAllOperations + 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 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 + } +} \ No newline at end of file diff --git a/src/main/groovy/grails/plugin/scaffolding/RestfulServiceController.groovy b/src/main/groovy/grails/plugin/scaffolding/RestfulServiceController.groovy new file mode 100644 index 0000000..263b350 --- /dev/null +++ b/src/main/groovy/grails/plugin/scaffolding/RestfulServiceController.groovy @@ -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 extends RestfulController { + + RestfulServiceController(Class 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 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) + } +} \ No newline at end of file diff --git a/src/main/groovy/grails/plugin/scaffolding/ScaffoldingViewResolver.groovy b/src/main/groovy/grails/plugin/scaffolding/ScaffoldingViewResolver.groovy index 91938af..0bba265 100644 --- a/src/main/groovy/grails/plugin/scaffolding/ScaffoldingViewResolver.groovy +++ b/src/main/groovy/grails/plugin/scaffolding/ScaffoldingViewResolver.groovy @@ -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 @@ -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 diff --git a/src/main/groovy/grails/plugin/scaffolding/annotation/Scaffold.java b/src/main/groovy/grails/plugin/scaffolding/annotation/Scaffold.java new file mode 100644 index 0000000..064cbd6 --- /dev/null +++ b/src/main/groovy/grails/plugin/scaffolding/annotation/Scaffold.java @@ -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; +} diff --git a/src/main/groovy/org/grails/compiler/scaffolding/ScaffoldingControllerInjector.groovy b/src/main/groovy/org/grails/compiler/scaffolding/ScaffoldingControllerInjector.groovy index 5f2e9ed..b44e53b 100644 --- a/src/main/groovy/org/grails/compiler/scaffolding/ScaffoldingControllerInjector.groovy +++ b/src/main/groovy/org/grails/compiler/scaffolding/ScaffoldingControllerInjector.groovy @@ -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 @@ -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() diff --git a/src/main/groovy/org/grails/compiler/scaffolding/ScaffoldingServiceInjector.groovy b/src/main/groovy/org/grails/compiler/scaffolding/ScaffoldingServiceInjector.groovy new file mode 100644 index 0000000..ab37cb5 --- /dev/null +++ b/src/main/groovy/org/grails/compiler/scaffolding/ScaffoldingServiceInjector.groovy @@ -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() + } +}