diff --git a/grails-doc/src/en/guide/upgrading/upgrading60x.adoc b/grails-doc/src/en/guide/upgrading/upgrading60x.adoc index 9ae9157f6b6..922c066df20 100644 --- a/grails-doc/src/en/guide/upgrading/upgrading60x.adoc +++ b/grails-doc/src/en/guide/upgrading/upgrading60x.adoc @@ -860,3 +860,49 @@ The `+` marker is opt-in and fully backward compatible: - Existing URL mappings without the `+` marker continue to work as before (splitting at the first dot) - No changes are required to existing applications - The feature can be adopted incrementally on a per-mapping basis + +===== 12.28 GormService API Changes + +The `grails.plugin.scaffolding.GormService` class has been updated to fix a thread-safety issue and improve API clarity. + +====== Changes + +1. The `resource` field type changed from `GormAllOperations` to `Class` +2. A new `gormStaticApi` field of type `GormAllOperations` was added with thread-safe lazy initialization using `@Lazy` +3. The constructor no longer instantiates the resource class - it now stores the Class reference directly + +====== Migration Impact + +If your code extends `GormService` or accesses its fields: + +**Accessing GORM operations**: Previously the `resource` field provided GORM operations. Now use the `gormStaticApi` field instead: + +[source,groovy] +---- +// Before (7.1.x and earlier) +class MyService extends GormService { + void myMethod() { + def result = resource.list() // resource was GormAllOperations + // ... + } +} + +// After (7.1.x with fix) +class MyService extends GormService { + void myMethod() { + def result = gormStaticApi.list() // use gormStaticApi instead + // ... + } +} +---- + +**Accessing the domain class**: If you need the Class reference, the `resource` field is now properly typed as `Class`: + +[source,groovy] +---- +// Before +Class clazz = resource.getClass() // awkward, resource was an instance + +// After +Class clazz = resource // resource is now the Class itself +---- diff --git a/grails-scaffolding/src/main/groovy/grails/plugin/scaffolding/DomainServiceLocator.java b/grails-scaffolding/src/main/groovy/grails/plugin/scaffolding/DomainServiceLocator.java index cef45755687..2205001c12b 100644 --- a/grails-scaffolding/src/main/groovy/grails/plugin/scaffolding/DomainServiceLocator.java +++ b/grails-scaffolding/src/main/groovy/grails/plugin/scaffolding/DomainServiceLocator.java @@ -33,7 +33,7 @@ /** * Resolves the appropriate service bean for a given domain class by: * - Scanning only beans of type GormService - * - Matching via: ((GormEntity) service.getResource()).instanceOf(domainClass) + * - Matching via: domainClass.isAssignableFrom(service.getResource()) * * Keeps a single shared cache for the whole app. */ @@ -72,28 +72,25 @@ private static > GormService findService(Class dom for (String name : names) { GormService gs = (GormService) ctx.getBean(name); - Object resource = gs.getResource(); - if (resource instanceof GormEntity) { - GormEntity ge = (GormEntity) resource; - if (ge.instanceOf(domainClass)) { - matchingBeanNames.add(name); - if (match != null) { - throw new IllegalStateException( - "Multiple GormService beans match domain " + domainClass.getName() + - ": " + matchingBeanNames - ); - } - @SuppressWarnings("unchecked") - GormService svc = (GormService) gs; - match = svc; + Class resourceClass = gs.getResource(); + if (resourceClass != null && domainClass.isAssignableFrom(resourceClass)) { + matchingBeanNames.add(name); + if (match != null) { + throw new IllegalStateException( + "Multiple GormService beans match domain " + domainClass.getName() + + ": " + matchingBeanNames + ); } + @SuppressWarnings("unchecked") + GormService svc = (GormService) gs; + match = svc; } } if (match == null) { throw new IllegalStateException( "No GormService bean found for domain " + domainClass.getName() + - " using resource.instanceOf(..). Scanned " + names.length + " GormService beans." + ". Scanned " + names.length + " GormService beans." ); } diff --git a/grails-scaffolding/src/main/groovy/grails/plugin/scaffolding/GormService.groovy b/grails-scaffolding/src/main/groovy/grails/plugin/scaffolding/GormService.groovy index ebd2be58f76..7ed8dbec29a 100644 --- a/grails-scaffolding/src/main/groovy/grails/plugin/scaffolding/GormService.groovy +++ b/grails-scaffolding/src/main/groovy/grails/plugin/scaffolding/GormService.groovy @@ -26,6 +26,7 @@ import grails.gorm.api.GormAllOperations import grails.gorm.transactions.ReadOnly import grails.gorm.transactions.Transactional import grails.util.GrailsNameUtils +import org.grails.datastore.gorm.GormEnhancer import org.grails.datastore.gorm.GormEntity import org.grails.datastore.gorm.GormEntityApi @@ -34,28 +35,30 @@ import org.grails.datastore.gorm.GormEntityApi @CompileStatic class GormService> { - GormAllOperations resource + @Lazy + GormAllOperations gormStaticApi = GormEnhancer.findStaticApi(resource) as GormAllOperations + Class resource String resourceName String resourceClassName boolean readOnly GormService(Class resource, boolean readOnly) { - this.resource = resource.getDeclaredConstructor().newInstance() as GormAllOperations + this.resource = resource this.readOnly = readOnly resourceClassName = resource.simpleName resourceName = GrailsNameUtils.getPropertyName(resource) } T get(Serializable id) { - resource.get(id) + gormStaticApi.get(id) } List list(Map args) { - resource.list(args) + gormStaticApi.list(args) } Long count(Map args) { - resource.count() + gormStaticApi.count() } @Transactional