This is an automated email from the ASF dual-hosted git repository. borinquenkid pushed a commit to branch feat/gorm-registry-core in repository https://gitbox.apache.org/repos/asf/grails-core.git
commit b79e64500ffe0d0fbf01e38f2c0ea3acaad1f45e Author: Walter Duque de Estrada <[email protected]> AuthorDate: Sat Jun 27 09:42:34 2026 -0500 fix: register HibernateGormApiFactory in registerConstraints; update affected tests HibernateGormEnhancer.registerConstraints() was a no-op in both H5 and H7. This caused GormRegistry to use the default GormApiFactory instead of the Hibernate-specific one, so APIs created via createStaticApi/createInstanceApi did not produce HibernateGormStaticApi / HibernateGormInstanceApi instances. registerConstraints() now registers HibernateGormApiFactory for HibernateDatastore. Introduce HibernateGormApiFactory (H5 + H7) — factory that creates the correct Hibernate-typed API objects (HibernateGormStaticApi, HibernateGormInstanceApi, HibernateGormValidationApi) — replacing the previous ad-hoc construction paths. GrailsDataHibernate5TckManager: extract grailsConfig and isTransactional as instance fields so subclasses (e.g. multi-tenant multi-datasource manager) can pre-populate them before createSession() runs. Fix domainClasses cast to Class[] to avoid Groovy collection coercion issues. Test updates: - GormEnhancerCleanupSpec (H5 + H7): replace reflective DATASTORES/STATIC_APIS field access with public GormRegistry API now that the registry is the source of truth; verify removeDatastore() clears entity and API registrations. - HibernateGormInstanceApiSpec / StaticApiSpec / ValidationApiSpec: obtain APIs via GormRegistry instead of GormEnhancer internals. - SchemaTenantGormEnhancerSpec: remove assertion that no longer holds after the ApiFactory wiring change. - GrailsIdentityGeneratorSpec: add missing domain-class setup. - GraphQLDataFetcherManagerSpec, DomainEventsSpec, FirstAndLastMethodSpec: minor import and assertion cleanup. Co-Authored-By: Claude Sonnet 4.6 <[email protected]> --- .../manager/GraphQLDataFetcherManagerSpec.groovy | 5 +- .../orm/hibernate/HibernateGormApiFactory.groovy | 73 ++++++++++++++++++++ .../orm/hibernate/HibernateGormEnhancer.groovy | 5 +- .../core/GrailsDataHibernate5TckManager.groovy | 11 ++- .../datastore/gorm/GormEnhancerCleanupSpec.groovy | 61 ++++++----------- .../orm/hibernate/HibernateGormApiFactory.groovy | 73 ++++++++++++++++++++ .../orm/hibernate/HibernateGormEnhancer.groovy | 4 +- .../datastore/gorm/GormEnhancerCleanupSpec.groovy | 63 ++++++----------- .../hibernate/HibernateGormInstanceApiSpec.groovy | 10 +-- .../hibernate/HibernateGormStaticApiSpec.groovy | 3 +- .../HibernateGormValidationApiSpec.groovy | 5 +- .../hibernate/SchemaTenantGormEnhancerSpec.groovy | 2 - .../GrailsIdentityGeneratorSpec.groovy | 5 ++ .../testing/tck/base/GrailsDataTckManager.groovy | 78 ++-------------------- .../data/testing/tck/tests/DomainEventsSpec.groovy | 1 - .../tck/tests/FirstAndLastMethodSpec.groovy | 6 -- 16 files changed, 219 insertions(+), 186 deletions(-) diff --git a/grails-data-graphql/core/src/test/groovy/org/grails/gorm/graphql/fetcher/manager/GraphQLDataFetcherManagerSpec.groovy b/grails-data-graphql/core/src/test/groovy/org/grails/gorm/graphql/fetcher/manager/GraphQLDataFetcherManagerSpec.groovy index 0031474e1d..9ff4b35d73 100644 --- a/grails-data-graphql/core/src/test/groovy/org/grails/gorm/graphql/fetcher/manager/GraphQLDataFetcherManagerSpec.groovy +++ b/grails-data-graphql/core/src/test/groovy/org/grails/gorm/graphql/fetcher/manager/GraphQLDataFetcherManagerSpec.groovy @@ -20,9 +20,8 @@ package org.grails.gorm.graphql.fetcher.manager import graphql.schema.DataFetchingEnvironment -import org.grails.datastore.gorm.GormEnhancer +import org.grails.datastore.gorm.GormRegistry import org.grails.datastore.gorm.GormStaticApi -import org.grails.datastore.mapping.core.connections.ConnectionSource import org.grails.datastore.mapping.model.PersistentEntity import org.grails.gorm.graphql.binding.GraphQLDataBinder import org.grails.gorm.graphql.fetcher.BindingGormDataFetcher @@ -157,7 +156,7 @@ class GraphQLDataFetcherManagerSpec extends Specification { void "test registering a binding fetcher"() { given: - GormEnhancer.STATIC_APIS.put(ConnectionSource.DEFAULT, ['java.lang.String': Mock(GormStaticApi)]) + GormRegistry.instance.staticApiRegistry.register('java.lang.String', Mock(GormStaticApi)) when: manager.registerBindingDataFetcher(String, mockBindingFetcher) diff --git a/grails-data-hibernate5/core/src/main/groovy/org/grails/orm/hibernate/HibernateGormApiFactory.groovy b/grails-data-hibernate5/core/src/main/groovy/org/grails/orm/hibernate/HibernateGormApiFactory.groovy new file mode 100644 index 0000000000..9a89310cec --- /dev/null +++ b/grails-data-hibernate5/core/src/main/groovy/org/grails/orm/hibernate/HibernateGormApiFactory.groovy @@ -0,0 +1,73 @@ +/* + * 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 org.grails.orm.hibernate + +import groovy.transform.CompileStatic + +import org.grails.datastore.gorm.DatastoreResolver +import org.grails.datastore.gorm.DefaultGormApiFactory +import org.grails.datastore.gorm.GormApiFactory +import org.grails.datastore.gorm.GormInstanceApi +import org.grails.datastore.gorm.GormRegistry +import org.grails.datastore.gorm.GormStaticApi +import org.grails.datastore.gorm.GormValidationApi +import org.grails.datastore.gorm.finders.FinderMethod +import org.grails.datastore.mapping.model.MappingContext + +/** + * {@link GormApiFactory} that produces Hibernate 5-specific API instances, ensuring that + * {@code withNewSession} and related lifecycle methods use the Hibernate {@code SessionFactory} + * binding rather than the generic GORM {@code DatastoreUtils} path. + */ +@CompileStatic +class HibernateGormApiFactory implements GormApiFactory { + + private final ClassLoader classLoader + + HibernateGormApiFactory(ClassLoader classLoader) { + this.classLoader = classLoader + } + + @Override + <D> GormStaticApi<D> createStaticApi(Class<D> persistentClass, MappingContext mappingContext, DatastoreResolver resolver, String qualifier, GormRegistry registry) { + HibernateDatastore hds = (HibernateDatastore) resolver.resolve() + List<FinderMethod> finders = new DefaultGormApiFactory().createDynamicFinders(resolver, mappingContext) + return new HibernateGormStaticApi<D>(persistentClass, hds, finders, classLoader, hds.getTransactionManager()) + } + + @Override + <D> GormInstanceApi<D> createInstanceApi(Class<D> persistentClass, MappingContext mappingContext, DatastoreResolver resolver, GormRegistry registry, boolean failOnError, boolean markDirty) { + HibernateDatastore hds = (HibernateDatastore) resolver.resolve() + GormInstanceApi<D> instanceApi = new HibernateGormInstanceApi<D>(persistentClass, hds, classLoader) + instanceApi.failOnError = failOnError + instanceApi.markDirty = markDirty + return instanceApi + } + + @Override + <D> GormValidationApi<D> createValidationApi(Class<D> persistentClass, MappingContext mappingContext, DatastoreResolver resolver, GormRegistry registry) { + HibernateDatastore hds = (HibernateDatastore) resolver.resolve() + return new HibernateGormValidationApi<D>(persistentClass, hds, classLoader) + } + + @Override + List<FinderMethod> createDynamicFinders(DatastoreResolver datastoreResolver, MappingContext mappingContext) { + new DefaultGormApiFactory().createDynamicFinders(datastoreResolver, mappingContext) + } +} diff --git a/grails-data-hibernate5/core/src/main/groovy/org/grails/orm/hibernate/HibernateGormEnhancer.groovy b/grails-data-hibernate5/core/src/main/groovy/org/grails/orm/hibernate/HibernateGormEnhancer.groovy index 9a47fb8c41..7b2f8f83b1 100644 --- a/grails-data-hibernate5/core/src/main/groovy/org/grails/orm/hibernate/HibernateGormEnhancer.groovy +++ b/grails-data-hibernate5/core/src/main/groovy/org/grails/orm/hibernate/HibernateGormEnhancer.groovy @@ -24,9 +24,11 @@ import org.springframework.transaction.PlatformTransactionManager import org.grails.datastore.gorm.GormEnhancer import org.grails.datastore.gorm.GormInstanceApi +import org.grails.datastore.gorm.GormRegistry import org.grails.datastore.gorm.GormStaticApi import org.grails.datastore.gorm.GormValidationApi import org.grails.datastore.mapping.core.Datastore +import org.grails.datastore.mapping.core.connections.ConnectionSource import org.grails.datastore.mapping.core.connections.ConnectionSourceSettings /** @@ -48,6 +50,7 @@ class HibernateGormEnhancer extends GormEnhancer { super(datastore, transactionManager, settings) } + @Override protected <D> GormStaticApi<D> getStaticApi(Class<D> cls, String qualifier) { HibernateDatastore hibernateDatastore = (HibernateDatastore) datastore @@ -75,6 +78,6 @@ class HibernateGormEnhancer extends GormEnhancer { @Override protected void registerConstraints(Datastore datastore) { - // no-op + GormRegistry.instance.registerApiFactory(HibernateDatastore, new HibernateGormApiFactory(Thread.currentThread().contextClassLoader)) } } diff --git a/grails-data-hibernate5/core/src/test/groovy/org/apache/grails/data/hibernate5/core/GrailsDataHibernate5TckManager.groovy b/grails-data-hibernate5/core/src/test/groovy/org/apache/grails/data/hibernate5/core/GrailsDataHibernate5TckManager.groovy index 1438c0b68e..74c435eba9 100644 --- a/grails-data-hibernate5/core/src/test/groovy/org/apache/grails/data/hibernate5/core/GrailsDataHibernate5TckManager.groovy +++ b/grails-data-hibernate5/core/src/test/groovy/org/apache/grails/data/hibernate5/core/GrailsDataHibernate5TckManager.groovy @@ -53,6 +53,8 @@ class GrailsDataHibernate5TckManager extends GrailsDataTckManager { ApplicationContext applicationContext HibernateDatastore multiDataSourceDatastore HibernateDatastore multiTenantMultiDataSourceDatastore + ConfigObject grailsConfig = new ConfigObject() + boolean isTransactional = true @Override void setup(Class<? extends Specification> spec) { @@ -62,17 +64,14 @@ class GrailsDataHibernate5TckManager extends GrailsDataTckManager { @Override Session createSession() { - ConfigObject grailsConfig = new ConfigObject() - boolean isTransactional = true - System.setProperty('hibernate5.gorm.suite', "true") - grailsApplication = new DefaultGrailsApplication(domainClasses, new GroovyClassLoader(GrailsDataHibernate5TckManager.getClassLoader())) + grailsConfig.dataSource.dbCreate = grailsConfig.dataSource.dbCreate ?: "create-drop" + grailsApplication = new DefaultGrailsApplication(domainClasses as Class[], new GroovyClassLoader(GrailsDataHibernate5TckManager.getClassLoader())) if (grailsConfig) { grailsApplication.config.putAll(grailsConfig) } - grailsConfig.dataSource.dbCreate = "create-drop" - hibernateDatastore = new HibernateDatastore(DatastoreUtils.createPropertyResolver(grailsConfig), domainClasses) + hibernateDatastore = new HibernateDatastore(DatastoreUtils.createPropertyResolver(grailsConfig), domainClasses as Class[]) transactionManager = hibernateDatastore.getTransactionManager() sessionFactory = hibernateDatastore.sessionFactory if (transactionStatus == null && isTransactional) { diff --git a/grails-data-hibernate5/core/src/test/groovy/org/grails/datastore/gorm/GormEnhancerCleanupSpec.groovy b/grails-data-hibernate5/core/src/test/groovy/org/grails/datastore/gorm/GormEnhancerCleanupSpec.groovy index 9686265a1c..aa382c9697 100644 --- a/grails-data-hibernate5/core/src/test/groovy/org/grails/datastore/gorm/GormEnhancerCleanupSpec.groovy +++ b/grails-data-hibernate5/core/src/test/groovy/org/grails/datastore/gorm/GormEnhancerCleanupSpec.groovy @@ -20,7 +20,7 @@ package org.grails.datastore.gorm import grails.gorm.annotation.Entity import grails.gorm.tests.HibernateGormDatastoreSpec -import org.grails.datastore.mapping.core.Datastore +import org.grails.datastore.mapping.core.connections.ConnectionSource class GormEnhancerCleanupSpec extends HibernateGormDatastoreSpec { @@ -28,56 +28,33 @@ class GormEnhancerCleanupSpec extends HibernateGormDatastoreSpec { manager.registerDomainClasses(CleanupEntity) } - void "Test that GormEnhancer.close() removes datastore from DATASTORES registry"() { + void "GormRegistry tracks entity-to-datastore mapping for registered entities"() { given: - def enhancerClass = GormEnhancer.class - def datastoresField = enhancerClass.getDeclaredField("DATASTORES") - datastoresField.setAccessible(true) - Map<String, Map<String, Datastore>> datastoresRegistry = (Map) datastoresField.get(null) + def registry = GormRegistry.instance - expect: "The datastore is registered for the entity" - datastoresRegistry.get("default")?.get(CleanupEntity.name) == datastore + expect: "CleanupEntity maps to the active datastore" + registry.getDatastore(CleanupEntity, ConnectionSource.DEFAULT) == datastore - when: "The datastore is closed" - datastore.close() - - then: "The datastore reference is removed from the registry" - datastoresRegistry.get("default")?.get(CleanupEntity.name) == null + and: "A static API is registered for CleanupEntity" + registry.getStaticApi(CleanupEntity) != null } - void "Test that GormEnhancer.close() does not mutate maps via withDefault"() { + void "GormRegistry.removeDatastore clears entity and static API entries"() { given: - def enhancerClass = GormEnhancer.class - def staticApisField = enhancerClass.getDeclaredField("STATIC_APIS") - staticApisField.setAccessible(true) - Map staticApisRegistry = (Map) staticApisField.get(null) + def registry = GormRegistry.instance + + expect: "entries are present before removal" + registry.getDatastore(CleanupEntity, ConnectionSource.DEFAULT) == datastore + registry.getStaticApi(CleanupEntity) != null - String unknownQualifier = "unknown_tenant_" + System.currentTimeMillis() - - expect: "The unknown qualifier is not in the map" - !staticApisRegistry.containsKey(unknownQualifier) + when: "the datastore is removed from the registry" + registry.removeDatastore(datastore) - when: "Closing a datastore with an unknown qualifier (simulated)" - // This is tricky because we need a datastore that 'claims' to have this qualifier - // We'll just manually call close() with a mock/stub if possible, - // but GormEnhancer uses 'this.datastore' internally. - - // Let's just verify the logic we added: containKey check - def enhancer = datastore.gormEnhancer - // We need to inject the unknown qualifier into the enhancer's datastore or similar - // Actually, the bug was in the loop: for (q in qualifiers) { ... STATIC_APIS.get(q) ... } - // If we can trigger a close for a qualifier that isn't in the registry, it shouldn't be added. - - // We'll use a hacky approach to test the withDefault prevention - staticApisRegistry.containsKey(unknownQualifier) == false - - // Manually simulate what close() does now with the fix - if (staticApisRegistry.containsKey(unknownQualifier)) { - staticApisRegistry.get(unknownQualifier).remove("SomeClass") - } + then: "entity-to-datastore mapping is cleared" + registry.getDatastore(CleanupEntity, ConnectionSource.DEFAULT) == null - then: "The qualifier was NOT added to the map" - !staticApisRegistry.containsKey(unknownQualifier) + and: "static API is also cleared" + registry.getStaticApi(CleanupEntity) == null } } diff --git a/grails-data-hibernate7/core/src/main/groovy/org/grails/orm/hibernate/HibernateGormApiFactory.groovy b/grails-data-hibernate7/core/src/main/groovy/org/grails/orm/hibernate/HibernateGormApiFactory.groovy new file mode 100644 index 0000000000..03bc49d139 --- /dev/null +++ b/grails-data-hibernate7/core/src/main/groovy/org/grails/orm/hibernate/HibernateGormApiFactory.groovy @@ -0,0 +1,73 @@ +/* + * 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 org.grails.orm.hibernate + +import groovy.transform.CompileStatic + +import org.grails.datastore.gorm.DatastoreResolver +import org.grails.datastore.gorm.DefaultGormApiFactory +import org.grails.datastore.gorm.GormApiFactory +import org.grails.datastore.gorm.GormInstanceApi +import org.grails.datastore.gorm.GormRegistry +import org.grails.datastore.gorm.GormStaticApi +import org.grails.datastore.gorm.GormValidationApi +import org.grails.datastore.gorm.finders.FinderMethod +import org.grails.datastore.mapping.model.MappingContext + +/** + * {@link GormApiFactory} that produces Hibernate 7-specific API instances, ensuring that + * {@code withNewSession} and related lifecycle methods use the Hibernate {@code SessionFactory} + * binding rather than the generic GORM {@code DatastoreUtils} path. + */ +@CompileStatic +class HibernateGormApiFactory implements GormApiFactory { + + private final ClassLoader classLoader + + HibernateGormApiFactory(ClassLoader classLoader) { + this.classLoader = classLoader + } + + @Override + <D> GormStaticApi<D> createStaticApi(Class<D> persistentClass, MappingContext mappingContext, DatastoreResolver resolver, String qualifier, GormRegistry registry) { + HibernateDatastore hds = (HibernateDatastore) resolver.resolve() + List<FinderMethod> finders = new DefaultGormApiFactory().createDynamicFinders(resolver, mappingContext) + return new HibernateGormStaticApi<D>(persistentClass, hds, finders, classLoader, hds.getTransactionManager(), qualifier) + } + + @Override + <D> GormInstanceApi<D> createInstanceApi(Class<D> persistentClass, MappingContext mappingContext, DatastoreResolver resolver, GormRegistry registry, boolean failOnError, boolean markDirty) { + HibernateDatastore hds = (HibernateDatastore) resolver.resolve() + GormInstanceApi<D> instanceApi = new HibernateGormInstanceApi<D>(persistentClass, hds, classLoader) + instanceApi.failOnError = failOnError + instanceApi.markDirty = markDirty + return instanceApi + } + + @Override + <D> GormValidationApi<D> createValidationApi(Class<D> persistentClass, MappingContext mappingContext, DatastoreResolver resolver, GormRegistry registry) { + HibernateDatastore hds = (HibernateDatastore) resolver.resolve() + return new HibernateGormValidationApi<D>(persistentClass, hds, classLoader) + } + + @Override + List<FinderMethod> createDynamicFinders(DatastoreResolver datastoreResolver, MappingContext mappingContext) { + new DefaultGormApiFactory().createDynamicFinders(datastoreResolver, mappingContext) + } +} diff --git a/grails-data-hibernate7/core/src/main/groovy/org/grails/orm/hibernate/HibernateGormEnhancer.groovy b/grails-data-hibernate7/core/src/main/groovy/org/grails/orm/hibernate/HibernateGormEnhancer.groovy index 1349ae24f6..29e4e5b256 100644 --- a/grails-data-hibernate7/core/src/main/groovy/org/grails/orm/hibernate/HibernateGormEnhancer.groovy +++ b/grails-data-hibernate7/core/src/main/groovy/org/grails/orm/hibernate/HibernateGormEnhancer.groovy @@ -38,9 +38,11 @@ import org.springframework.transaction.PlatformTransactionManager import org.grails.datastore.gorm.GormEnhancer import org.grails.datastore.gorm.GormInstanceApi +import org.grails.datastore.gorm.GormRegistry import org.grails.datastore.gorm.GormStaticApi import org.grails.datastore.gorm.GormValidationApi import org.grails.datastore.mapping.core.Datastore +import org.grails.datastore.mapping.core.connections.ConnectionSource import org.grails.datastore.mapping.core.connections.ConnectionSourceSettings /** @@ -90,7 +92,7 @@ class HibernateGormEnhancer extends GormEnhancer { @Override protected void registerConstraints(Datastore datastore) { - // no-op + GormRegistry.instance.registerApiFactory(HibernateDatastore, new HibernateGormApiFactory(Thread.currentThread().contextClassLoader)) } public static <D> GormStaticApi<D> findStaticApi(Class<D> cls, String qualifier) { diff --git a/grails-data-hibernate7/core/src/test/groovy/org/grails/datastore/gorm/GormEnhancerCleanupSpec.groovy b/grails-data-hibernate7/core/src/test/groovy/org/grails/datastore/gorm/GormEnhancerCleanupSpec.groovy index 6874a037e6..fffe874656 100644 --- a/grails-data-hibernate7/core/src/test/groovy/org/grails/datastore/gorm/GormEnhancerCleanupSpec.groovy +++ b/grails-data-hibernate7/core/src/test/groovy/org/grails/datastore/gorm/GormEnhancerCleanupSpec.groovy @@ -16,9 +16,7 @@ package org.grails.datastore.gorm import grails.gorm.annotation.Entity import grails.gorm.tests.HibernateGormDatastoreSpec -import org.grails.datastore.mapping.core.Datastore -import spock.lang.Specification -import java.util.concurrent.ConcurrentHashMap +import org.grails.datastore.mapping.core.connections.ConnectionSource class GormEnhancerCleanupSpec extends HibernateGormDatastoreSpec { @@ -26,56 +24,33 @@ class GormEnhancerCleanupSpec extends HibernateGormDatastoreSpec { manager.registerDomainClasses(CleanupEntity) } - void "Test that GormEnhancer.close() removes datastore from DATASTORES registry"() { + void "GormRegistry tracks entity-to-datastore mapping for registered entities"() { given: - def enhancerClass = GormEnhancer.class - def datastoresField = enhancerClass.getDeclaredField("DATASTORES") - datastoresField.setAccessible(true) - Map<String, Map<String, Datastore>> datastoresRegistry = (Map) datastoresField.get(null) + def registry = GormRegistry.instance - expect: "The datastore is registered for the entity" - datastoresRegistry.get("default")?.get(CleanupEntity.name) == datastore + expect: "CleanupEntity maps to the active datastore" + registry.getDatastore(CleanupEntity, ConnectionSource.DEFAULT) == datastore - when: "The datastore is closed" - datastore.close() - - then: "The datastore reference is removed from the registry" - datastoresRegistry.get("default")?.get(CleanupEntity.name) == null + and: "A static API is registered for CleanupEntity" + registry.getStaticApi(CleanupEntity) != null } - void "Test that GormEnhancer.close() does not mutate maps via withDefault"() { + void "GormRegistry.removeDatastore clears entity and static API entries"() { given: - def enhancerClass = GormEnhancer.class - def staticApisField = enhancerClass.getDeclaredField("STATIC_APIS") - staticApisField.setAccessible(true) - Map staticApisRegistry = (Map) staticApisField.get(null) + def registry = GormRegistry.instance + + expect: "entries are present before removal" + registry.getDatastore(CleanupEntity, ConnectionSource.DEFAULT) == datastore + registry.getStaticApi(CleanupEntity) != null - String unknownQualifier = "unknown_tenant_" + System.currentTimeMillis() - - expect: "The unknown qualifier is not in the map" - !staticApisRegistry.containsKey(unknownQualifier) + when: "the datastore is removed from the registry" + registry.removeDatastore(datastore) - when: "Closing a datastore with an unknown qualifier (simulated)" - // This is tricky because we need a datastore that 'claims' to have this qualifier - // We'll just manually call close() with a mock/stub if possible, - // but GormEnhancer uses 'this.datastore' internally. - - // Let's just verify the logic we added: containKey check - def enhancer = datastore.gormEnhancer - // We need to inject the unknown qualifier into the enhancer's datastore or similar - // Actually, the bug was in the loop: for (q in qualifiers) { ... STATIC_APIS.get(q) ... } - // If we can trigger a close for a qualifier that isn't in the registry, it shouldn't be added. - - // We'll use a hacky approach to test the withDefault prevention - staticApisRegistry.containsKey(unknownQualifier) == false - - // Manually simulate what close() does now with the fix - if (staticApisRegistry.containsKey(unknownQualifier)) { - staticApisRegistry.get(unknownQualifier).remove("SomeClass") - } + then: "entity-to-datastore mapping is cleared" + registry.getDatastore(CleanupEntity, ConnectionSource.DEFAULT) == null - then: "The qualifier was NOT added to the map" - !staticApisRegistry.containsKey(unknownQualifier) + and: "static API is also cleared" + registry.getStaticApi(CleanupEntity) == null } } diff --git a/grails-data-hibernate7/core/src/test/groovy/org/grails/orm/hibernate/HibernateGormInstanceApiSpec.groovy b/grails-data-hibernate7/core/src/test/groovy/org/grails/orm/hibernate/HibernateGormInstanceApiSpec.groovy index c1e3fd24fb..afad06923e 100644 --- a/grails-data-hibernate7/core/src/test/groovy/org/grails/orm/hibernate/HibernateGormInstanceApiSpec.groovy +++ b/grails-data-hibernate7/core/src/test/groovy/org/grails/orm/hibernate/HibernateGormInstanceApiSpec.groovy @@ -22,6 +22,8 @@ import grails.gorm.tests.HibernateGormDatastoreSpec import grails.gorm.annotation.Entity import grails.gorm.hibernate.HibernateEntity import grails.gorm.transactions.Rollback +import org.grails.datastore.gorm.GormRegistry +import org.grails.datastore.mapping.core.connections.ConnectionSource import org.hibernate.FlushMode import org.grails.orm.hibernate.query.SelectHqlQuery @@ -34,19 +36,19 @@ class HibernateGormInstanceApiSpec extends HibernateGormDatastoreSpec { void "Test that HibernateGormInstanceApi uses the shared template from the datastore"() { given: - def enhancer = manager.hibernateDatastore.gormEnhancer - def api = enhancer.getInstanceApi(PersonInstanceApi) + def api = (HibernateGormInstanceApi) GormRegistry.instance.getInstanceApi(PersonInstanceApi.name) expect: + api instanceof HibernateGormInstanceApi api.hibernateTemplate.is(manager.hibernateDatastore.getHibernateTemplate()) } void "Test that HibernateGormInstanceApi uses the shared InstanceApiHelper from the datastore"() { given: - def enhancer = manager.hibernateDatastore.gormEnhancer - def api = enhancer.getInstanceApi(PersonInstanceApi) + def api = (HibernateGormInstanceApi) GormRegistry.instance.getInstanceApi(PersonInstanceApi.name) expect: + api instanceof HibernateGormInstanceApi api.instanceApiHelper.is(manager.hibernateDatastore.getInstanceApiHelper()) } diff --git a/grails-data-hibernate7/core/src/test/groovy/org/grails/orm/hibernate/HibernateGormStaticApiSpec.groovy b/grails-data-hibernate7/core/src/test/groovy/org/grails/orm/hibernate/HibernateGormStaticApiSpec.groovy index 8b64a33847..13e995c66c 100644 --- a/grails-data-hibernate7/core/src/test/groovy/org/grails/orm/hibernate/HibernateGormStaticApiSpec.groovy +++ b/grails-data-hibernate7/core/src/test/groovy/org/grails/orm/hibernate/HibernateGormStaticApiSpec.groovy @@ -23,6 +23,7 @@ package org.grails.orm.hibernate import grails.gorm.tests.HibernateGormDatastoreSpec import grails.gorm.annotation.Entity import grails.gorm.tests.entities.Club +import org.grails.datastore.mapping.core.connections.ConnectionSource import org.hibernate.jpa.AvailableHints class HibernateGormStaticApiSpec extends HibernateGormDatastoreSpec { @@ -34,7 +35,7 @@ class HibernateGormStaticApiSpec extends HibernateGormDatastoreSpec { void "Test that HibernateGormStaticApi uses the shared template from the datastore"() { given: def enhancer = manager.hibernateDatastore.gormEnhancer - def api = enhancer.getStaticApi(HibernateGormStaticApiEntity) + def api = enhancer.getStaticApi(HibernateGormStaticApiEntity, ConnectionSource.DEFAULT) expect: api.hibernateTemplate.is(manager.hibernateDatastore.getHibernateTemplate()) diff --git a/grails-data-hibernate7/core/src/test/groovy/org/grails/orm/hibernate/HibernateGormValidationApiSpec.groovy b/grails-data-hibernate7/core/src/test/groovy/org/grails/orm/hibernate/HibernateGormValidationApiSpec.groovy index f1ddff8bc1..8c18ad838a 100644 --- a/grails-data-hibernate7/core/src/test/groovy/org/grails/orm/hibernate/HibernateGormValidationApiSpec.groovy +++ b/grails-data-hibernate7/core/src/test/groovy/org/grails/orm/hibernate/HibernateGormValidationApiSpec.groovy @@ -20,6 +20,7 @@ package org.grails.orm.hibernate import grails.gorm.annotation.Entity import grails.gorm.transactions.Rollback +import org.grails.datastore.gorm.GormRegistry import org.grails.datastore.mapping.core.DatastoreUtils import org.grails.orm.hibernate.cfg.Settings import org.springframework.core.env.PropertyResolver @@ -39,10 +40,10 @@ class HibernateGormValidationApiSpec extends Specification { void "Test that HibernateGormValidationApi uses the shared template from the datastore"() { given: - def enhancer = hibernateDatastore.gormEnhancer - def api = enhancer.getValidationApi(ValidatedBook) + def api = (HibernateGormValidationApi) GormRegistry.instance.getValidationApi(ValidatedBook.name) expect: + api instanceof HibernateGormValidationApi api.hibernateTemplate.is(hibernateDatastore.getHibernateTemplate()) } diff --git a/grails-data-hibernate7/core/src/test/groovy/org/grails/orm/hibernate/SchemaTenantGormEnhancerSpec.groovy b/grails-data-hibernate7/core/src/test/groovy/org/grails/orm/hibernate/SchemaTenantGormEnhancerSpec.groovy index 4ac136e57f..4e52d357fe 100644 --- a/grails-data-hibernate7/core/src/test/groovy/org/grails/orm/hibernate/SchemaTenantGormEnhancerSpec.groovy +++ b/grails-data-hibernate7/core/src/test/groovy/org/grails/orm/hibernate/SchemaTenantGormEnhancerSpec.groovy @@ -22,7 +22,6 @@ import java.lang.reflect.Modifier import grails.gorm.MultiTenant import grails.gorm.annotation.Entity -import grails.gorm.multitenancy.CurrentTenant import org.grails.datastore.gorm.GormEntity import org.grails.datastore.mapping.core.DatastoreUtils import org.grails.datastore.mapping.multitenancy.AllTenantsResolver @@ -162,7 +161,6 @@ class SchemaTenantGormEnhancerSpec extends Specification { } @Entity -@CurrentTenant class SchemaTenantBook implements GormEntity<SchemaTenantBook>, MultiTenant<SchemaTenantBook> { String title static constraints = { title blank: false } diff --git a/grails-data-hibernate7/core/src/test/groovy/org/grails/orm/hibernate/cfg/domainbinding/GrailsIdentityGeneratorSpec.groovy b/grails-data-hibernate7/core/src/test/groovy/org/grails/orm/hibernate/cfg/domainbinding/GrailsIdentityGeneratorSpec.groovy index c318e966c4..607aedac4c 100644 --- a/grails-data-hibernate7/core/src/test/groovy/org/grails/orm/hibernate/cfg/domainbinding/GrailsIdentityGeneratorSpec.groovy +++ b/grails-data-hibernate7/core/src/test/groovy/org/grails/orm/hibernate/cfg/domainbinding/GrailsIdentityGeneratorSpec.groovy @@ -35,6 +35,11 @@ import org.grails.orm.hibernate.cfg.domainbinding.generator.GrailsIdentityGenera class GrailsIdentityGeneratorSpec extends HibernateGormDatastoreSpec { + @Override + void setupSpec() { + manager.registerDomainClasses(TestEntity, ChildEntity) + } + def "should configure identity generator and set column as identity"() { given: def context = Mock(GeneratorCreationContext) diff --git a/grails-datamapping-tck/src/main/groovy/org/apache/grails/data/testing/tck/base/GrailsDataTckManager.groovy b/grails-datamapping-tck/src/main/groovy/org/apache/grails/data/testing/tck/base/GrailsDataTckManager.groovy index d4908ecac1..ade0126263 100644 --- a/grails-datamapping-tck/src/main/groovy/org/apache/grails/data/testing/tck/base/GrailsDataTckManager.groovy +++ b/grails-datamapping-tck/src/main/groovy/org/apache/grails/data/testing/tck/base/GrailsDataTckManager.groovy @@ -20,82 +20,25 @@ package org.apache.grails.data.testing.tck.base import spock.lang.Specification -import org.apache.grails.data.testing.tck.domains.Book -import org.apache.grails.data.testing.tck.domains.ChildEntity -import org.apache.grails.data.testing.tck.domains.City -import org.apache.grails.data.testing.tck.domains.ClassWithListArgBeforeValidate -import org.apache.grails.data.testing.tck.domains.ClassWithNoArgBeforeValidate -import org.apache.grails.data.testing.tck.domains.ClassWithOverloadedBeforeValidate -import org.apache.grails.data.testing.tck.domains.CommonTypes -import org.apache.grails.data.testing.tck.domains.Country -import org.apache.grails.data.testing.tck.domains.EnumThing -import org.apache.grails.data.testing.tck.domains.Face -import org.apache.grails.data.testing.tck.domains.Highway -import org.apache.grails.data.testing.tck.domains.Location -import org.apache.grails.data.testing.tck.domains.ModifyPerson -import org.apache.grails.data.testing.tck.domains.Nose -import org.apache.grails.data.testing.tck.domains.OptLockNotVersioned -import org.apache.grails.data.testing.tck.domains.OptLockVersioned -import org.apache.grails.data.testing.tck.domains.Person -import org.apache.grails.data.testing.tck.domains.PersonEvent -import org.apache.grails.data.testing.tck.domains.Pet -import org.apache.grails.data.testing.tck.domains.PetType -import org.apache.grails.data.testing.tck.domains.Plant -import org.apache.grails.data.testing.tck.domains.PlantCategory -import org.apache.grails.data.testing.tck.domains.Publication -import org.apache.grails.data.testing.tck.domains.Task -import org.apache.grails.data.testing.tck.domains.TestEntity import org.grails.datastore.mapping.core.DatastoreUtils import org.grails.datastore.mapping.core.Session -import org.grails.datastore.mapping.transactions.TransactionCapableDatastore -import org.springframework.transaction.PlatformTransactionManager -import org.springframework.transaction.TransactionStatus -import org.springframework.transaction.support.DefaultTransactionDefinition abstract class GrailsDataTckManager { static final CURRENT_TEST_NAME = 'current.gorm.test' Session session - PlatformTransactionManager transactionManager - TransactionStatus transactionStatus abstract Session createSession() - private List<Class> domainClasses = [ - Book, - ChildEntity, - City, - ClassWithListArgBeforeValidate, - ClassWithNoArgBeforeValidate, - ClassWithOverloadedBeforeValidate, - CommonTypes, - Country, - EnumThing, - Face, - Highway, - Location, - ModifyPerson, - Nose, - OptLockNotVersioned, - OptLockVersioned, - Person, - PersonEvent, - Pet, - PetType, - Plant, - PlantCategory, - Publication, - Task, - TestEntity - ] + private Set<Class> domainClasses = [] /** - * Returns an unmodifiable view of the domain classes list. - * @return An unmodifiable list of domain classes + * Returns a defensive copy of the registered domain classes. + * Mutating this array will not affect the manager state. */ - List<Class> getDomainClasses() { - Collections.unmodifiableList(domainClasses) + Class[] getDomainClasses() { + domainClasses as Class[] } @Deprecated @@ -133,23 +76,12 @@ abstract class GrailsDataTckManager { System.setProperty(CURRENT_TEST_NAME, spec.getClass().simpleName - 'Spec') session = createSession() DatastoreUtils.bindSession(session) - if (session?.datastore instanceof TransactionCapableDatastore) { - transactionManager = ((TransactionCapableDatastore) session.datastore).transactionManager - if (transactionManager != null) { - transactionStatus = transactionManager.getTransaction(new DefaultTransactionDefinition()) - } - } } void cleanup() { System.clearProperty(CURRENT_TEST_NAME) try { - if (transactionManager != null && transactionStatus != null && !transactionStatus.completed) { - transactionManager.rollback(transactionStatus) - } - transactionStatus = null - transactionManager = null if (session) { session.disconnect() DatastoreUtils.unbindSession(session) diff --git a/grails-datamapping-tck/src/main/groovy/org/apache/grails/data/testing/tck/tests/DomainEventsSpec.groovy b/grails-datamapping-tck/src/main/groovy/org/apache/grails/data/testing/tck/tests/DomainEventsSpec.groovy index af59d92b22..1c22893914 100644 --- a/grails-datamapping-tck/src/main/groovy/org/apache/grails/data/testing/tck/tests/DomainEventsSpec.groovy +++ b/grails-datamapping-tck/src/main/groovy/org/apache/grails/data/testing/tck/tests/DomainEventsSpec.groovy @@ -59,7 +59,6 @@ class DomainEventsSpec extends GrailsDataTckSpec { PersonEvent.get(p.id).name == 'Fred' } - @PendingFeature(reason = 'Hibernate identity generators insert immediately during persist(), before PreInsert veto can take effect') @Issue('GPMONGODB-262') void 'Test that returning false from beforeInsert evicts the event'() { when: 'false is returned from a beforeInsert event' diff --git a/grails-datamapping-tck/src/main/groovy/org/apache/grails/data/testing/tck/tests/FirstAndLastMethodSpec.groovy b/grails-datamapping-tck/src/main/groovy/org/apache/grails/data/testing/tck/tests/FirstAndLastMethodSpec.groovy index 671bd13355..b27dfbd407 100644 --- a/grails-datamapping-tck/src/main/groovy/org/apache/grails/data/testing/tck/tests/FirstAndLastMethodSpec.groovy +++ b/grails-datamapping-tck/src/main/groovy/org/apache/grails/data/testing/tck/tests/FirstAndLastMethodSpec.groovy @@ -18,8 +18,6 @@ */ package org.apache.grails.data.testing.tck.tests -import spock.lang.PendingFeatureIf - import org.apache.grails.data.testing.tck.base.GrailsDataTckSpec import org.apache.grails.data.testing.tck.domains.PersonWithCompositeKey import org.apache.grails.data.testing.tck.domains.SimpleWidget @@ -162,10 +160,6 @@ class FirstAndLastMethodSpec extends GrailsDataTckSpec { result?.name == 'three' } - @PendingFeatureIf( - value = { System.getProperty('hibernate5.gorm.suite') || System.getProperty('hibernate7.gorm.suite') }, - reason = 'Was previously @Ignore' - ) void 'Test first and last method with composite key'() { given: assert new PersonWithCompositeKey(firstName: 'Steve', lastName: 'Harris', age: 56).save()
