This is an automated email from the ASF dual-hosted git repository. borinquenkid pushed a commit to branch 8.0.x-hibernate7 in repository https://gitbox.apache.org/repos/asf/grails-core.git
commit b7f8ef4318a3c7a8e07468b9c9d3f320fdbacea7 Author: Walter Duque de Estrada <[email protected]> AuthorDate: Sat Feb 21 21:16:42 2026 -0600 1) HibernateMappingContext.isDomainClass static method moved to GrailsHibernateUtil 2) Refactored GrailsJpaMappingConfigurationStrategy to its own class 3) Refactored HibernateIdentityMapping to its own class --- .../orm/hibernate/cfg/GrailsHibernateUtil.java | 53 ++++++ .../orm/hibernate/cfg/HibernateMappingContext.java | 118 ++----------- .../GrailsJpaMappingConfigurationStrategy.groovy | 41 +++++ .../hibernate/HibernateIdentityMapping.java | 82 +++++++++ .../cfg/HibernateMappingContextSpec.groovy | 196 +++++++++++++++++---- 5 files changed, 355 insertions(+), 135 deletions(-) diff --git a/grails-data-hibernate7/core/src/main/groovy/org/grails/orm/hibernate/cfg/GrailsHibernateUtil.java b/grails-data-hibernate7/core/src/main/groovy/org/grails/orm/hibernate/cfg/GrailsHibernateUtil.java index c841717916..9d0749912b 100644 --- a/grails-data-hibernate7/core/src/main/groovy/org/grails/orm/hibernate/cfg/GrailsHibernateUtil.java +++ b/grails-data-hibernate7/core/src/main/groovy/org/grails/orm/hibernate/cfg/GrailsHibernateUtil.java @@ -18,9 +18,14 @@ */ package org.grails.orm.hibernate.cfg; +import java.lang.annotation.Annotation; + +import groovy.lang.Closure; import groovy.lang.GroovyObject; import groovy.lang.GroovySystem; import groovy.lang.MetaClass; + +import org.grails.datastore.gorm.GormEntity; import org.grails.datastore.mapping.model.PersistentEntity; import org.grails.datastore.mapping.model.config.GormProperties; import org.grails.orm.hibernate.cfg.domainbinding.hibernate.GrailsHibernatePersistentEntity; @@ -39,6 +44,8 @@ import org.slf4j.Logger; import org.slf4j.LoggerFactory; import org.springframework.transaction.support.TransactionSynchronizationManager; +import grails.gorm.annotation.Entity; + /** * Utility methods for configuring Hibernate inside Grails. * @@ -211,4 +218,50 @@ public class GrailsHibernateUtil extends HibernateRuntimeUtils { public static String unqualify(final String qualifiedName) { return StringHelper.unqualify(qualifiedName); } + + public static boolean isDomainClass(Class clazz) { + if (GormEntity.class.isAssignableFrom(clazz)) { + return true; + } + + // it's not a closure + if (Closure.class.isAssignableFrom(clazz)) { + return false; + } + + if (((Class<?>) clazz).isEnum()) return false; + + Annotation[] allAnnotations = ((Class<?>) clazz).getAnnotations(); + for (Annotation annotation : allAnnotations) { + Class<? extends Annotation> type = annotation.annotationType(); + String annName = type.getName(); + if (annName.equals("grails.persistence.Entity")) { + return true; + } + if (type.equals(Entity.class)) { + return true; + } + } + + Class<?> testClass = (Class<?>) clazz; + while (testClass != null + && !testClass.equals(GroovyObject.class) + && !testClass.equals(Object.class)) { + try { + // make sure the identify and version field exist + testClass.getDeclaredField(GormProperties.IDENTITY); + testClass.getDeclaredField(GormProperties.VERSION); + + // passes all conditions return true + return true; + } catch (SecurityException e) { + // ignore + } catch (NoSuchFieldException e) { + // ignore + } + testClass = testClass.getSuperclass(); + } + + return false; + } } diff --git a/grails-data-hibernate7/core/src/main/groovy/org/grails/orm/hibernate/cfg/HibernateMappingContext.java b/grails-data-hibernate7/core/src/main/groovy/org/grails/orm/hibernate/cfg/HibernateMappingContext.java index c5dd4662f9..87a21e3462 100644 --- a/grails-data-hibernate7/core/src/main/groovy/org/grails/orm/hibernate/cfg/HibernateMappingContext.java +++ b/grails-data-hibernate7/core/src/main/groovy/org/grails/orm/hibernate/cfg/HibernateMappingContext.java @@ -18,30 +18,29 @@ */ package org.grails.orm.hibernate.cfg; -import grails.gorm.annotation.Entity; import grails.gorm.hibernate.HibernateEntity; import groovy.lang.Closure; -import groovy.lang.GroovyObject; + import java.beans.PropertyDescriptor; -import java.lang.annotation.Annotation; import java.util.ArrayList; import java.util.Collection; -import java.util.Optional; +import java.util.List; + import org.grails.datastore.gorm.GormEntity; import org.grails.datastore.mapping.config.AbstractGormMappingFactory; import org.grails.datastore.mapping.config.Property; import org.grails.datastore.mapping.config.groovy.MappingConfigurationBuilder; import org.grails.datastore.mapping.engine.types.CustomTypeMarshaller; import org.grails.datastore.mapping.model.*; -import org.grails.datastore.mapping.model.config.GormProperties; -import org.grails.datastore.mapping.model.config.JpaMappingConfigurationStrategy; import org.grails.datastore.mapping.reflect.ClassUtils; import org.grails.orm.hibernate.cfg.domainbinding.hibernate.GrailsHibernatePersistentEntity; +import org.grails.orm.hibernate.cfg.domainbinding.hibernate.GrailsJpaMappingConfigurationStrategy; import org.grails.orm.hibernate.cfg.domainbinding.hibernate.HibernateBasicProperty; import org.grails.orm.hibernate.cfg.domainbinding.hibernate.HibernateCustomProperty; import org.grails.orm.hibernate.cfg.domainbinding.hibernate.HibernateEmbeddedCollectionProperty; import org.grails.orm.hibernate.cfg.domainbinding.hibernate.HibernateEmbeddedPersistentEntity; import org.grails.orm.hibernate.cfg.domainbinding.hibernate.HibernateEmbeddedProperty; +import org.grails.orm.hibernate.cfg.domainbinding.hibernate.HibernateIdentityMapping; import org.grails.orm.hibernate.cfg.domainbinding.hibernate.HibernateIdentityProperty; import org.grails.orm.hibernate.cfg.domainbinding.hibernate.HibernateManyToManyProperty; import org.grails.orm.hibernate.cfg.domainbinding.hibernate.HibernateManyToOneProperty; @@ -53,7 +52,6 @@ import org.grails.orm.hibernate.cfg.domainbinding.hibernate.HibernateSimplePrope import org.grails.orm.hibernate.cfg.domainbinding.hibernate.HibernateTenantIdProperty; import org.grails.orm.hibernate.connections.HibernateConnectionSourceSettings; import org.grails.orm.hibernate.proxy.HibernateProxyHandler; -import org.springframework.validation.Errors; /** * A Mapping context for Hibernate @@ -63,7 +61,6 @@ import org.springframework.validation.Errors; */ public class HibernateMappingContext extends AbstractMappingContext { - private static final String[] DEFAULT_IDENTITY_MAPPING = new String[] {GormProperties.IDENTITY}; private final HibernateMappingFactory mappingFactory; private final MappingConfigurationStrategy syntaxStrategy; @@ -88,13 +85,7 @@ public class HibernateMappingContext extends AbstractMappingContext { this.mappingFactory.setDefaultConstraints(settings.getDefault().getConstraints()); } this.mappingFactory.setContextObject(contextObject); - this.syntaxStrategy = - new JpaMappingConfigurationStrategy(mappingFactory) { - @Override - protected boolean supportsCustomType(Class<?> propertyType) { - return !Errors.class.isAssignableFrom(propertyType); - } - }; + this.syntaxStrategy = new GrailsJpaMappingConfigurationStrategy(mappingFactory); this.proxyFactory = new HibernateProxyHandler(); addPersistentEntities(persistentClasses); } @@ -149,56 +140,6 @@ public class HibernateMappingContext extends AbstractMappingContext { return createPersistentEntity(javaClass); } - public static boolean isDomainClass(Class clazz) { - return doIsDomainClassCheck(clazz); - } - - private static boolean doIsDomainClassCheck(Class<?> clazz) { - if (GormEntity.class.isAssignableFrom(clazz)) { - return true; - } - - // it's not a closure - if (Closure.class.isAssignableFrom(clazz)) { - return false; - } - - if (clazz.isEnum()) return false; - - Annotation[] allAnnotations = clazz.getAnnotations(); - for (Annotation annotation : allAnnotations) { - Class<? extends Annotation> type = annotation.annotationType(); - String annName = type.getName(); - if (annName.equals("grails.persistence.Entity")) { - return true; - } - if (type.equals(Entity.class)) { - return true; - } - } - - Class<?> testClass = clazz; - while (testClass != null - && !testClass.equals(GroovyObject.class) - && !testClass.equals(Object.class)) { - try { - // make sure the identify and version field exist - testClass.getDeclaredField(GormProperties.IDENTITY); - testClass.getDeclaredField(GormProperties.VERSION); - - // passes all conditions return true - return true; - } catch (SecurityException e) { - // ignore - } catch (NoSuchFieldException e) { - // ignore - } - testClass = testClass.getSuperclass(); - } - - return false; - } - @Override public PersistentEntity createEmbeddedEntity(Class type) { HibernateEmbeddedPersistentEntity embedded = new HibernateEmbeddedPersistentEntity(type, this); @@ -217,11 +158,16 @@ public class HibernateMappingContext extends AbstractMappingContext { public Collection<GrailsHibernatePersistentEntity> getHibernatePersistentEntities( String dataSourceName) { - return Optional.ofNullable(persistentEntities).orElse(new ArrayList<>()).stream() - .filter(GrailsHibernatePersistentEntity.class::isInstance) - .map(GrailsHibernatePersistentEntity.class::cast) - .peek(persistentEntity -> persistentEntity.setDataSourceName(dataSourceName)) - .toList(); + List<GrailsHibernatePersistentEntity> result = new ArrayList<>(); + if (persistentEntities != null) { + for (PersistentEntity entity : persistentEntities) { + if (entity instanceof GrailsHibernatePersistentEntity hibernateEntity) { + hibernateEntity.setDataSourceName(dataSourceName); + result.add(hibernateEntity); + } + } + } + return result; } class HibernateMappingFactory extends AbstractGormMappingFactory<Mapping, PropertyConfig> { @@ -383,37 +329,7 @@ public class HibernateMappingContext extends AbstractMappingContext { } else { generator = ValueGenerator.AUTO; } - return new IdentityMapping() { - @Override - public String[] getIdentifierName() { - if (identity instanceof Identity) { - final String name = ((Identity) identity).getName(); - if (name != null) { - return new String[] {name}; - } else { - return DEFAULT_IDENTITY_MAPPING; - } - } else if (identity instanceof CompositeIdentity) { - return ((CompositeIdentity) identity).getPropertyNames(); - } - return DEFAULT_IDENTITY_MAPPING; - } - - @Override - public ValueGenerator getGenerator() { - return generator; - } - - @Override - public ClassMapping getClassMapping() { - return classMapping; - } - - @Override - public Property getMappedForm() { - return (Property) identity; - } - }; + return new HibernateIdentityMapping(identity, generator, classMapping); } @Override diff --git a/grails-data-hibernate7/core/src/main/groovy/org/grails/orm/hibernate/cfg/domainbinding/hibernate/GrailsJpaMappingConfigurationStrategy.groovy b/grails-data-hibernate7/core/src/main/groovy/org/grails/orm/hibernate/cfg/domainbinding/hibernate/GrailsJpaMappingConfigurationStrategy.groovy new file mode 100644 index 0000000000..9fd538e3ed --- /dev/null +++ b/grails-data-hibernate7/core/src/main/groovy/org/grails/orm/hibernate/cfg/domainbinding/hibernate/GrailsJpaMappingConfigurationStrategy.groovy @@ -0,0 +1,41 @@ +/* + * 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.cfg.domainbinding.hibernate + +import groovy.transform.CompileStatic +import org.grails.datastore.mapping.model.MappingFactory +import org.grails.datastore.mapping.model.config.JpaMappingConfigurationStrategy +import org.springframework.validation.Errors + +/** + * A {@link JpaMappingConfigurationStrategy} for Grails/Hibernate that excludes + * Spring {@link Errors} from being treated as custom types. + */ +@CompileStatic +class GrailsJpaMappingConfigurationStrategy extends JpaMappingConfigurationStrategy { + + GrailsJpaMappingConfigurationStrategy(MappingFactory propertyFactory) { + super(propertyFactory) + } + + @Override + protected boolean supportsCustomType(Class<?> propertyType) { + !Errors.isAssignableFrom(propertyType) + } +} diff --git a/grails-data-hibernate7/core/src/main/groovy/org/grails/orm/hibernate/cfg/domainbinding/hibernate/HibernateIdentityMapping.java b/grails-data-hibernate7/core/src/main/groovy/org/grails/orm/hibernate/cfg/domainbinding/hibernate/HibernateIdentityMapping.java new file mode 100644 index 0000000000..11761684fc --- /dev/null +++ b/grails-data-hibernate7/core/src/main/groovy/org/grails/orm/hibernate/cfg/domainbinding/hibernate/HibernateIdentityMapping.java @@ -0,0 +1,82 @@ +/* + * 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.cfg.domainbinding.hibernate; + +import org.grails.datastore.mapping.config.Property; +import org.grails.datastore.mapping.model.ClassMapping; +import org.grails.datastore.mapping.model.IdentityMapping; +import org.grails.datastore.mapping.model.ValueGenerator; +import org.grails.orm.hibernate.cfg.CompositeIdentity; +import org.grails.orm.hibernate.cfg.Identity; + +/** + * {@link IdentityMapping} implementation for Hibernate that resolves identifier names + * from {@link Identity} and {@link CompositeIdentity} mapped forms. + */ +public class HibernateIdentityMapping implements IdentityMapping<Property> { + + private static final String[] DEFAULT_IDENTITY_MAPPING = new String[] {"id"}; + + private final Object identity; + private final ValueGenerator generator; + private final ClassMapping classMapping; + + /** + * Constructs a HibernateIdentityMapping. + * + * @param identity the identity mapped form ({@link Identity} or {@link CompositeIdentity}) + * @param generator the resolved {@link ValueGenerator} + * @param classMapping the owning {@link ClassMapping} + */ + public HibernateIdentityMapping(Object identity, ValueGenerator generator, ClassMapping classMapping) { + this.identity = identity; + this.generator = generator; + this.classMapping = classMapping; + } + + @Override + public String[] getIdentifierName() { + if (identity instanceof Identity) { + final String name = ((Identity) identity).getName(); + if (name != null) { + return new String[] {name}; + } else { + return DEFAULT_IDENTITY_MAPPING; + } + } else if (identity instanceof CompositeIdentity) { + return ((CompositeIdentity) identity).getPropertyNames(); + } + return DEFAULT_IDENTITY_MAPPING; + } + + @Override + public ValueGenerator getGenerator() { + return generator; + } + + @Override + public ClassMapping getClassMapping() { + return classMapping; + } + + @Override + public Property getMappedForm() { + return (Property) identity; + } +} diff --git a/grails-data-hibernate7/core/src/test/groovy/org/grails/orm/hibernate/cfg/HibernateMappingContextSpec.groovy b/grails-data-hibernate7/core/src/test/groovy/org/grails/orm/hibernate/cfg/HibernateMappingContextSpec.groovy index 4a1a1940f2..d5f78349f5 100644 --- a/grails-data-hibernate7/core/src/test/groovy/org/grails/orm/hibernate/cfg/HibernateMappingContextSpec.groovy +++ b/grails-data-hibernate7/core/src/test/groovy/org/grails/orm/hibernate/cfg/HibernateMappingContextSpec.groovy @@ -19,65 +19,193 @@ package org.grails.orm.hibernate.cfg import grails.gorm.annotation.Entity +import grails.gorm.hibernate.HibernateEntity +import grails.gorm.specs.HibernateGormDatastoreSpec import org.grails.datastore.mapping.engine.types.AbstractMappingAwareCustomTypeMarshaller import org.grails.datastore.mapping.model.PersistentEntity import org.grails.datastore.mapping.model.PersistentProperty import org.grails.datastore.mapping.model.ValueGenerator +import org.grails.datastore.mapping.model.config.JpaMappingConfigurationStrategy +import org.grails.orm.hibernate.cfg.domainbinding.hibernate.GrailsHibernatePersistentEntity +import org.grails.orm.hibernate.cfg.domainbinding.hibernate.GrailsJpaMappingConfigurationStrategy import org.grails.orm.hibernate.connections.HibernateConnectionSourceSettings -import spock.lang.Specification +import org.springframework.validation.Errors -/** - * Created by graemerocher on 07/10/2016. - */ -class HibernateMappingContextSpec extends Specification { +class HibernateMappingContextSpec extends HibernateGormDatastoreSpec { + + def setupSpec() { + manager.addAllDomainClasses([MappingContextBook, MappingContextAuthor, MappingContextAddress]) + } - void "test entity with custom id generator"() { - when:"A context is created" - def mappingContext = new HibernateMappingContext() - PersistentEntity entity = mappingContext.addPersistentEntity(CustomIdGeneratorEntity) + // --- unit-style tests (no datastore required) --- - then:"The mapping is correct" - entity.mapping.identifier.generator == ValueGenerator.CUSTOM + void "default constructor creates a usable context"() { + when: + def ctx = new HibernateMappingContext() + + then: + ctx.mappingFactory != null + ctx.getMappingSyntaxStrategy() instanceof JpaMappingConfigurationStrategy } - void "test entity with custom type marshaller is registered correctly"() { - given:"A configured custom type marshaller" + void "custom type marshaller is registered on the mapping factory"() { + given: HibernateConnectionSourceSettings settings = new HibernateConnectionSourceSettings() - settings.custom.types = [new MyTypeMarshaller(MyUUIDGenerator)] + settings.custom.types = [new MappingContextTypeMarshaller(MappingContextUUID)] + + when: + def ctx = new HibernateMappingContext(settings) + + then: + ctx.mappingFactory.isCustomType(MappingContextUUID) + } + + void "entity with custom id generator resolves to ValueGenerator.CUSTOM"() { + when: + def ctx = new HibernateMappingContext() + PersistentEntity entity = ctx.addPersistentEntity(CustomIdGeneratorEntity) + + then: + entity.mapping.identifier.generator == ValueGenerator.CUSTOM + } + + void "Errors type is not treated as a custom type by the syntax strategy"() { + when: + def ctx = new HibernateMappingContext() + def strategy = ctx.getMappingSyntaxStrategy() as GrailsJpaMappingConfigurationStrategy + + then: + !strategy.supportsCustomType(Errors) + } - when:"A context is created" - def mappingContext = new HibernateMappingContext(settings) + void "arbitrary non-Errors type is supported as a custom type by the syntax strategy"() { + when: + def ctx = new HibernateMappingContext() + def strategy = ctx.getMappingSyntaxStrategy() as GrailsJpaMappingConfigurationStrategy - then:"The mapping is created successfully" - mappingContext - mappingContext.mappingFactory + then: + strategy.supportsCustomType(MappingContextUUID) + } + + void "getPersistentEntity strips Hibernate proxy suffix"() { + when: + def ctx = new HibernateMappingContext() + ctx.addPersistentEntity(CustomIdGeneratorEntity) + + then: + ctx.getPersistentEntity("org.grails.orm.hibernate.cfg.CustomIdGeneratorEntity\$HibernateProxy\$XYZ") != null + } + + void "non-GormEntity class is not added as a persistent entity"() { + when: + def ctx = new HibernateMappingContext() + def entity = ctx.addPersistentEntity(MappingContextUUID) + + then: + entity == null + } + + // --- integration-style tests (use live datastore) --- - and:"The type is registered as a custom type with the mapping factory" - mappingContext.mappingFactory.isCustomType(MyUUIDGenerator) + void "mappingContext is a HibernateMappingContext"() { + expect: + mappingContext instanceof HibernateMappingContext + } + + void "registered domain classes appear as persistent entities"() { + expect: + mappingContext.getPersistentEntity(MappingContextBook.name) != null + mappingContext.getPersistentEntity(MappingContextAuthor.name) != null + } + + void "getHibernatePersistentEntities returns GrailsHibernatePersistentEntity instances"() { + when: + def entities = mappingContext.getHibernatePersistentEntities("DEFAULT") + + then: + entities.every { it instanceof GrailsHibernatePersistentEntity } + entities.every { it.dataSourceName == "DEFAULT" } + } + + void "getHibernatePersistentEntities sets the dataSourceName on each entity"() { + when: + def entities = mappingContext.getHibernatePersistentEntities("myDs") + + then: + entities.every { it.dataSourceName == "myDs" } + } + + void "embedded entity is created correctly"() { + when: + def embedded = mappingContext.createEmbeddedEntity(MappingContextAddress) + + then: + embedded != null + embedded.javaClass == MappingContextAddress + } + + void "MappingContextBook has expected persistent properties"() { + when: + PersistentEntity entity = mappingContext.getPersistentEntity(MappingContextBook.name) + + then: + entity.persistentProperties.find { it.name == "title" } != null + entity.persistentProperties.find { it.name == "author" } != null + } + + void "MappingContextAuthor oneToMany relationship is mapped"() { + when: + PersistentEntity entity = mappingContext.getPersistentEntity(MappingContextAuthor.name) + + then: + entity.persistentProperties.find { it.name == "books" } != null + } + + void "getMappingFactory returns the HibernateMappingFactory"() { + expect: + mappingContext.mappingFactory != null + mappingContext.mappingFactory.class.simpleName == "HibernateMappingFactory" } } +// --- domain classes used in integration tests --- + +@Entity +class MappingContextBook implements HibernateEntity<MappingContextBook> { + String title + MappingContextAuthor author + static belongsTo = [author: MappingContextAuthor] +} + +@Entity +class MappingContextAuthor implements HibernateEntity<MappingContextAuthor> { + String name + static hasMany = [books: MappingContextBook] +} + +class MappingContextAddress { + String street + String city +} + +// --- helpers for unit tests --- + @Entity class CustomIdGeneratorEntity { String name static mapping = { - id(generator: "org.grails.orm.hibernate.cfg.MyUUIDGenerator", type: "uuid-binary") + id(generator: "org.grails.orm.hibernate.cfg.MappingContextUUID", type: "uuid-binary") } } -class MyUUIDGenerator { -} +class MappingContextUUID {} + +class MappingContextTypeMarshaller extends AbstractMappingAwareCustomTypeMarshaller { + MappingContextTypeMarshaller(Class targetType) { super(targetType) } -class MyTypeMarshaller extends AbstractMappingAwareCustomTypeMarshaller { - MyTypeMarshaller(Class targetType) { - super(targetType) - } @Override - protected Object writeInternal(PersistentProperty property, String key, Object value, Object nativeTarget) { - return value - } + protected Object writeInternal(PersistentProperty property, String key, Object value, Object nativeTarget) { value } + @Override - protected Object readInternal(PersistentProperty property, String key, Object nativeSource) { - return nativeSource - } + protected Object readInternal(PersistentProperty property, String key, Object nativeSource) { nativeSource } } \ No newline at end of file
