Skip to content
Merged
Show file tree
Hide file tree
Changes from 10 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
Original file line number Diff line number Diff line change
Expand Up @@ -32,7 +32,6 @@ import org.grails.io.support.Resource
* Creates a scaffolded controller.
* Usage: <code>./gradlew runCommand "-Pargs=create-scaffold-controller [DOMAIN_CLASS_NAME]"</code>
*
* @author Puneet Behl
* @since 5.0.0
*/
@CompileStatic
Expand All @@ -56,11 +55,25 @@ class CreateScaffoldControllerCommand implements GrailsApplicationCommand, Comma
}
boolean overwrite = isFlagPresent('force')
final Model model = model(sourceClass)

String namespace = flag('namespace')
boolean useService = isFlagPresent('service')

Map<String, Object> templateModel = model.asMap()
templateModel.put('useService', useService)
templateModel.put('namespace', namespace ?: '')

String destinationPath = "grails-app/controllers/${model.packagePath}"

if (namespace) {
destinationPath = "${destinationPath}/${namespace}"
}

render(template: template('scaffolding/ScaffoldedController.groovy'),
destination: file("grails-app/controllers/${model.packagePath}/${model.convention('Controller')}.groovy"),
model: model,
destination: file("${destinationPath}/${model.convention('Controller')}.groovy"),
model: templateModel,
overwrite: overwrite)
verbose('Scaffold controller created for class domain-class')
verbose('Scaffold controller created for domain class')

return SUCCESS
}
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,67 @@
/*
* Licensed to the Apache Software Foundation (ASF) under one
* or more contributor license agreements. See the NOTICE file
* distributed with this work for additional information
* regarding copyright ownership. The ASF licenses this file
* to you under the Apache License, Version 2.0 (the
* "License"); you may not use this file except in compliance
* with the License. You may obtain a copy of the License at
*
* https://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing,
* software distributed under the License is distributed on an
* "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
* KIND, either express or implied. See the License for the
* specific language governing permissions and limitations
* under the License.
*/
package scaffolding

import groovy.transform.CompileStatic

import grails.build.logging.ConsoleLogger
import grails.build.logging.GrailsConsole
import grails.codegen.model.Model
import grails.dev.commands.GrailsApplicationCommand
import grails.plugin.scaffolding.CommandLineHelper
import grails.plugin.scaffolding.SkipBootstrap
import org.grails.io.support.Resource

/**
* Creates a scaffolded service.
* Usage: <code>./gradlew runCommand "-Pargs=create-scaffold-service [DOMAIN_CLASS_NAME]"</code>
*
* @author Scott Murphy Heiberg
* @since 7.1.0
*/
@CompileStatic
class CreateScaffoldServiceCommand implements GrailsApplicationCommand, CommandLineHelper, SkipBootstrap {

String description = 'Creates a scaffolded service'

@Delegate
ConsoleLogger consoleLogger = GrailsConsole.getInstance()

boolean handle() {
final String domainClassName = args[0]
if (!domainClassName) {
error('No domain-class specified')
return FAILURE
}
final Resource sourceClass = source(domainClassName)
if (!sourceClass) {
error("No domain-class found for name: ${domainClassName}")
return FAILURE
}
boolean overwrite = isFlagPresent('force')
final Model model = model(sourceClass)
render(template: template('scaffolding/ScaffoldedService.groovy'),
destination: file("grails-app/services/${model.packagePath}/${model.convention('Service')}.groovy"),
model: model,
overwrite: overwrite)
verbose('Scaffold service created for domain class')

return SUCCESS
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,88 @@
/*
* Licensed to the Apache Software Foundation (ASF) under one
* or more contributor license agreements. See the NOTICE file
* distributed with this work for additional information
* regarding copyright ownership. The ASF licenses this file
* to you under the Apache License, Version 2.0 (the
* "License"); you may not use this file except in compliance
* with the License. You may obtain a copy of the License at
*
* https://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing,
* software distributed under the License is distributed on an
* "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
* KIND, either express or implied. See the License for the
* specific language governing permissions and limitations
* under the License.
*/
package scaffolding

import groovy.transform.CompileStatic

import grails.build.logging.ConsoleLogger
import grails.build.logging.GrailsConsole
import grails.codegen.model.Model
import grails.dev.commands.GrailsApplicationCommand
import grails.plugin.scaffolding.CommandLineHelper
import grails.plugin.scaffolding.SkipBootstrap
import org.grails.io.support.Resource

/**
* Generates a scaffolded service and controller.
* Usage: <code>./gradlew runCommand "-Pargs=generate-scaffold-all [DOMAIN_CLASS_NAME]"</code>
*
* @author Scott Murphy Heiberg
* @since 7.1.0
*/
@CompileStatic
class GenerateScaffoldAllCommand implements GrailsApplicationCommand, CommandLineHelper, SkipBootstrap {

String description = 'Generates a scaffolded service and controller'

@Delegate
ConsoleLogger consoleLogger = GrailsConsole.getInstance()

boolean handle() {
final String domainClassName = args[0]
if (!domainClassName) {
error('No domain-class specified')
return FAILURE
}
final Resource sourceClass = source(domainClassName)
if (!sourceClass) {
error("No domain-class found for name: ${domainClassName}")
return FAILURE
}
boolean overwrite = isFlagPresent('force')
final Model model = model(sourceClass)

String namespace = flag('namespace')

// Generate scaffolded service
render(template: template('scaffolding/ScaffoldedService.groovy'),
destination: file("grails-app/services/${model.packagePath}/${model.convention('Service')}.groovy"),
model: model,
overwrite: overwrite)
verbose('Scaffold service created for domain class')

// Generate scaffolded controller with service reference
Map<String, Object> templateModel = model.asMap()
templateModel.put('useService', true)
templateModel.put('namespace', namespace ?: '')

String controllerDestinationPath = "grails-app/controllers/${model.packagePath}"

if (namespace) {
controllerDestinationPath = "${controllerDestinationPath}/${namespace}"
}

render(template: template('scaffolding/ScaffoldedController.groovy'),
destination: file("${controllerDestinationPath}/${model.convention('Controller')}.groovy"),
model: templateModel,
overwrite: overwrite)
verbose('Scaffold controller created for domain class with service reference')

return SUCCESS
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -39,4 +39,13 @@ trait CommandLineHelper {
}
}

String flag(String name) {
final CommandLine commandLine = executionContext.commandLine
if (commandLine.hasOption(name)) {
return commandLine.optionValue(name)?.toString()
} else {
return commandLine?.undeclaredOptions?.get(name)?.toString()
}
}

}
Original file line number Diff line number Diff line change
Expand Up @@ -33,7 +33,7 @@ import org.grails.datastore.gorm.GormEntityApi
@Artefact('Service')
@ReadOnly
@CompileStatic
class GormService<T extends GormEntity<T>> {
class GormService<T extends GormEntity<T>> implements ScaffoldService<T, Serializable> {

@Lazy
GormAllOperations<T> gormStaticApi = GormEnhancer.findStaticApi(resource) as GormAllOperations<T>
Expand All @@ -49,18 +49,22 @@ class GormService<T extends GormEntity<T>> {
resourceName = GrailsNameUtils.getPropertyName(resource)
}

@Override
T get(Serializable id) {
gormStaticApi.get(id)
}

@Override
List<T> list(Map args) {
gormStaticApi.list(args)
}

@Override
Long count(Map args) {
gormStaticApi.count()
}

@Override
@Transactional
void delete(Serializable id) {
if (readOnly) {
Expand All @@ -69,6 +73,7 @@ class GormService<T extends GormEntity<T>> {
((GormEntityApi) get(id)).delete(flush: true)
}

@Override
@Transactional
T save(T instance) {
if (readOnly) {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -25,6 +25,33 @@ import grails.gorm.transactions.ReadOnly
import grails.rest.RestfulController
import org.grails.datastore.gorm.GormEntity

/**
* Restful controller that delegates all operations to a scaffold service.
*
* <p>This controller is datastore-agnostic and works with any {@link ScaffoldService}
* implementation (GORM, JPA, JDBC, REST, custom, etc.). It uses the service interface
* rather than concrete implementations, allowing different backends to be swapped.</p>
*
* <p>Read-only protection is handled by the service layer - services with {@code readOnly=true}
* will silently ignore mutation operations (no-op behavior).</p>
*
* <h3>Example Usage</h3>
* <pre>{@code
* @Scaffold(RestfulServiceController<Car>)
* class CarController {}
* }</pre>
*
* <p>The controller will automatically locate and inject the corresponding service
* (e.g., {@code CarService}) using {@link DomainServiceLocator}.</p>
*
* @param <T> The domain/entity type
*
* @author Scott Murphy Heiberg
* @since 7.1.0
*
* @see ScaffoldService
* @see DomainServiceLocator
*/
@Artefact('Controller')
@ReadOnly
@CompileStatic
Expand All @@ -34,7 +61,12 @@ class RestfulServiceController<T extends GormEntity<T>> extends RestfulControlle
super(resource, readOnly)
}

protected GormService<T> getService() {
/**
* Get the scaffold service for this controller.
*
* @return The scaffold service (resolved via {@link DomainServiceLocator})
*/
protected ScaffoldService<T, Serializable> getService() {
DomainServiceLocator.<T>resolve(resource)
}

Expand Down
Loading
Loading