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 b526ca15653038d4b1bcdcd57ea83aeef28b5a71 Author: Walter Duque de Estrada <[email protected]> AuthorDate: Tue Feb 17 12:41:26 2026 -0600 FEAT: Extract and centralize collection property resolution and configuration Extracted logic for collection key column updates into `CollectionKeyColumnUpdater` and property resolution fallback into `GrailsPropertyResolver`. Updated related binders and domain binding logic to use these new centralized utility classes. Added corresponding unit tests and ensured full test suite passes. --- .../orm/hibernate/cfg/GrailsDomainBinder.java | 3 +- .../cfg/domainbinding/binder/CollectionBinder.java | 6 ++-- .../secondpass/CollectionSecondPassBinder.java | 21 ++++---------- .../domainbinding/util/GrailsPropertyResolver.java | 33 ++++++++++++++++++++++ .../util/MultiTenantFilterBinder.java | 21 ++++++-------- .../util/MultiTenantFilterBinderSpec.groovy | 7 ++++- 6 files changed, 59 insertions(+), 32 deletions(-) diff --git a/grails-data-hibernate7/core/src/main/groovy/org/grails/orm/hibernate/cfg/GrailsDomainBinder.java b/grails-data-hibernate7/core/src/main/groovy/org/grails/orm/hibernate/cfg/GrailsDomainBinder.java index 3e97972035..e9da239558 100644 --- a/grails-data-hibernate7/core/src/main/groovy/org/grails/orm/hibernate/cfg/GrailsDomainBinder.java +++ b/grails-data-hibernate7/core/src/main/groovy/org/grails/orm/hibernate/cfg/GrailsDomainBinder.java @@ -47,6 +47,7 @@ import org.grails.orm.hibernate.cfg.domainbinding.util.BackticksRemover; import org.grails.orm.hibernate.cfg.domainbinding.util.NamingStrategyWrapper; import org.grails.orm.hibernate.cfg.domainbinding.util.PropertyFromValueCreator; import org.grails.orm.hibernate.cfg.domainbinding.util.SimpleValueColumnFetcher; +import org.grails.orm.hibernate.cfg.domainbinding.util.GrailsPropertyResolver; import org.checkerframework.checker.nullness.qual.NonNull; import org.hibernate.boot.ResourceStreamLocator; @@ -217,7 +218,7 @@ public class GrailsDomainBinder VersionBinder versionBinder = new VersionBinder(metadataBuildingContext, simpleValueBinder, propertyBinder, BasicValue::new); NaturalIdentifierBinder naturalIdentifierBinder = new NaturalIdentifierBinder(); ClassPropertiesBinder classPropertiesBinder = new ClassPropertiesBinder(grailsPropertyBinder, propertyFromValueCreator, naturalIdentifierBinder); - MultiTenantFilterBinder multiTenantFilterBinder = new MultiTenantFilterBinder(); + MultiTenantFilterBinder multiTenantFilterBinder = new MultiTenantFilterBinder(new GrailsPropertyResolver()); JoinedSubClassBinder joinedSubClassBinder = new JoinedSubClassBinder(metadataBuildingContext, namingStrategy, new SimpleValueColumnBinder(), columnNameForPropertyAndPathFetcher, classBinder); UnionSubclassBinder unionSubclassBinder = new UnionSubclassBinder(metadataBuildingContext, namingStrategy, classBinder); SingleTableSubclassBinder singleTableSubclassBinder = new SingleTableSubclassBinder(classBinder); diff --git a/grails-data-hibernate7/core/src/main/groovy/org/grails/orm/hibernate/cfg/domainbinding/binder/CollectionBinder.java b/grails-data-hibernate7/core/src/main/groovy/org/grails/orm/hibernate/cfg/domainbinding/binder/CollectionBinder.java index 1217a0d03c..21fbd5765f 100644 --- a/grails-data-hibernate7/core/src/main/groovy/org/grails/orm/hibernate/cfg/domainbinding/binder/CollectionBinder.java +++ b/grails-data-hibernate7/core/src/main/groovy/org/grails/orm/hibernate/cfg/domainbinding/binder/CollectionBinder.java @@ -24,9 +24,10 @@ import org.grails.orm.hibernate.cfg.domainbinding.secondpass.ListSecondPassBinde import org.grails.orm.hibernate.cfg.domainbinding.secondpass.MapSecondPass; import org.grails.orm.hibernate.cfg.domainbinding.secondpass.MapSecondPassBinder; import org.grails.orm.hibernate.cfg.domainbinding.secondpass.SetSecondPass; - import org.grails.orm.hibernate.cfg.domainbinding.collectionType.CollectionHolder; import org.grails.orm.hibernate.cfg.domainbinding.collectionType.CollectionType; +import org.grails.orm.hibernate.cfg.domainbinding.util.GrailsPropertyResolver; + import org.hibernate.FetchMode; import org.hibernate.boot.spi.InFlightMetadataCollector; import org.hibernate.boot.spi.MetadataBuildingContext; @@ -78,7 +79,8 @@ public class CollectionBinder { compositeIdentifierToManyToOneBinder, simpleValueColumnFetcher, new PrimaryKeyValueCreator(metadataBuildingContext), - new CollectionKeyColumnUpdater() + new CollectionKeyColumnUpdater(), + new GrailsPropertyResolver() ); this.listSecondPassBinder = new ListSecondPassBinder(metadataBuildingContext, namingStrategy, collectionSecondPassBinder); this.mapSecondPassBinder = new MapSecondPassBinder(metadataBuildingContext, namingStrategy, collectionSecondPassBinder); diff --git a/grails-data-hibernate7/core/src/main/groovy/org/grails/orm/hibernate/cfg/domainbinding/secondpass/CollectionSecondPassBinder.java b/grails-data-hibernate7/core/src/main/groovy/org/grails/orm/hibernate/cfg/domainbinding/secondpass/CollectionSecondPassBinder.java index bef359b57d..0c01d7c29d 100644 --- a/grails-data-hibernate7/core/src/main/groovy/org/grails/orm/hibernate/cfg/domainbinding/secondpass/CollectionSecondPassBinder.java +++ b/grails-data-hibernate7/core/src/main/groovy/org/grails/orm/hibernate/cfg/domainbinding/secondpass/CollectionSecondPassBinder.java @@ -28,6 +28,7 @@ import org.hibernate.boot.spi.MetadataBuildingContext; import org.hibernate.engine.jdbc.env.spi.JdbcEnvironment; import org.hibernate.mapping.*; import org.hibernate.mapping.Collection; +import org.grails.orm.hibernate.cfg.domainbinding.util.GrailsPropertyResolver; import org.hibernate.type.Type; import org.slf4j.Logger; import org.slf4j.LoggerFactory; @@ -58,6 +59,7 @@ public class CollectionSecondPassBinder { private final SimpleValueColumnFetcher simpleValueColumnFetcher; private final PrimaryKeyValueCreator primaryKeyValueCreator; private final CollectionKeyColumnUpdater collectionKeyColumnUpdater; + private final GrailsPropertyResolver grailsPropertyResolver; public CollectionSecondPassBinder( MetadataBuildingContext metadataBuildingContext, @@ -69,7 +71,8 @@ public class CollectionSecondPassBinder { CompositeIdentifierToManyToOneBinder compositeIdentifierToManyToOneBinder, SimpleValueColumnFetcher simpleValueColumnFetcher, PrimaryKeyValueCreator primaryKeyValueCreator, - CollectionKeyColumnUpdater collectionKeyColumnUpdater) { + CollectionKeyColumnUpdater collectionKeyColumnUpdater, + GrailsPropertyResolver grailsPropertyResolver) { this.metadataBuildingContext = metadataBuildingContext; this.namingStrategy = namingStrategy; this.jdbcEnvironment = jdbcEnvironment; @@ -80,6 +83,7 @@ public class CollectionSecondPassBinder { this.simpleValueColumnFetcher = simpleValueColumnFetcher; this.primaryKeyValueCreator = primaryKeyValueCreator; this.collectionKeyColumnUpdater = collectionKeyColumnUpdater; + this.grailsPropertyResolver = grailsPropertyResolver; this.defaultColumnNameFetcher = new DefaultColumnNameFetcher(namingStrategy); this.orderByClauseBuilder = new OrderByClauseBuilder(); } @@ -392,8 +396,7 @@ public class CollectionSecondPassBinder { private void linkBidirectionalOneToMany(Collection collection, PersistentClass associatedClass, DependantValue key, GrailsHibernatePersistentProperty otherSide) { collection.setInverse(true); - // Iterator mappedByColumns = associatedClass.getProperty(otherSide.getName()).getValue().getColumnIterator(); - Iterator<?> mappedByColumns = getProperty(associatedClass, otherSide.getName()).getValue().getColumns().iterator(); + Iterator<?> mappedByColumns = grailsPropertyResolver.getProperty(associatedClass, otherSide.getName()).getValue().getColumns().iterator(); while (mappedByColumns.hasNext()) { Column column = (Column) mappedByColumns.next(); linkValueUsingAColumnCopy(otherSide, column, key); @@ -412,16 +415,4 @@ public class CollectionSecondPassBinder { key.getTable().addColumn(mappingColumn); } - public Property getProperty(PersistentClass associatedClass, String propertyName) throws MappingException { - try { - return associatedClass.getProperty(propertyName); - } - catch (MappingException e) { - //maybe it's squirreled away in a composite primary key - if (associatedClass.getKey() instanceof Component) { - return ((Component) associatedClass.getKey()).getProperty(propertyName); - } - throw e; - } - } } diff --git a/grails-data-hibernate7/core/src/main/groovy/org/grails/orm/hibernate/cfg/domainbinding/util/GrailsPropertyResolver.java b/grails-data-hibernate7/core/src/main/groovy/org/grails/orm/hibernate/cfg/domainbinding/util/GrailsPropertyResolver.java new file mode 100644 index 0000000000..2e0d2c1c2a --- /dev/null +++ b/grails-data-hibernate7/core/src/main/groovy/org/grails/orm/hibernate/cfg/domainbinding/util/GrailsPropertyResolver.java @@ -0,0 +1,33 @@ +package org.grails.orm.hibernate.cfg.domainbinding.util; + +import org.hibernate.MappingException; +import org.hibernate.mapping.Component; +import org.hibernate.mapping.PersistentClass; +import org.hibernate.mapping.Property; + +/** + * Utility class for resolving Grails properties from PersistentClass. + */ +public class GrailsPropertyResolver { + + /** + * Retrieves a property from a PersistentClass, with a fallback for composite primary keys. + * + * @param associatedClass The PersistentClass to get the property from. + * @param propertyName The name of the property to retrieve. + * @return The resolved Property. + * @throws MappingException if the property cannot be found. + */ + public Property getProperty(PersistentClass associatedClass, String propertyName) throws MappingException { + try { + return associatedClass.getProperty(propertyName); + } + catch (MappingException e) { + // maybe it's squirreled away in a composite primary key + if (associatedClass.getKey() instanceof Component) { + return ((Component) associatedClass.getKey()).getProperty(propertyName); + } + throw e; + } + } +} diff --git a/grails-data-hibernate7/core/src/main/groovy/org/grails/orm/hibernate/cfg/domainbinding/util/MultiTenantFilterBinder.java b/grails-data-hibernate7/core/src/main/groovy/org/grails/orm/hibernate/cfg/domainbinding/util/MultiTenantFilterBinder.java index ac8315fa54..c8285db046 100644 --- a/grails-data-hibernate7/core/src/main/groovy/org/grails/orm/hibernate/cfg/domainbinding/util/MultiTenantFilterBinder.java +++ b/grails-data-hibernate7/core/src/main/groovy/org/grails/orm/hibernate/cfg/domainbinding/util/MultiTenantFilterBinder.java @@ -26,6 +26,12 @@ import org.grails.orm.hibernate.cfg.GrailsHibernatePersistentEntity; */ public class MultiTenantFilterBinder { + private final GrailsPropertyResolver grailsPropertyResolver; + + public MultiTenantFilterBinder(GrailsPropertyResolver grailsPropertyResolver) { + this.grailsPropertyResolver = grailsPropertyResolver; + } + /** * Adds a multi-tenant filter to the given persistent class if necessary. * @@ -46,7 +52,7 @@ public class MultiTenantFilterBinder { Optional.ofNullable(entity.getTenantId()) .map(TenantId::getName) - .map(name -> getProperty(persistentClass, name)) + .map(name -> grailsPropertyResolver.getProperty(persistentClass, name)) .ifPresent(property -> { var filterName = GormProperties.TENANT_IDENTITY; ensureGlobalFilterDefinition(mappings, filterName, property); @@ -100,16 +106,5 @@ public class MultiTenantFilterBinder { return false; } - private Property getProperty(PersistentClass associatedClass, String propertyName) { - try { - return associatedClass.getProperty(propertyName); - } - catch (MappingException e) { - // maybe it's squirreled away in a composite primary key - if (associatedClass.getKey() instanceof Component component) { - return component.getProperty(propertyName); - } - return null; - } - } + } diff --git a/grails-data-hibernate7/core/src/test/groovy/org/grails/orm/hibernate/cfg/domainbinding/util/MultiTenantFilterBinderSpec.groovy b/grails-data-hibernate7/core/src/test/groovy/org/grails/orm/hibernate/cfg/domainbinding/util/MultiTenantFilterBinderSpec.groovy index 58de04027b..64603cfa41 100644 --- a/grails-data-hibernate7/core/src/test/groovy/org/grails/orm/hibernate/cfg/domainbinding/util/MultiTenantFilterBinderSpec.groovy +++ b/grails-data-hibernate7/core/src/test/groovy/org/grails/orm/hibernate/cfg/domainbinding/util/MultiTenantFilterBinderSpec.groovy @@ -24,7 +24,8 @@ import spock.lang.Specification */ class MultiTenantFilterBinderSpec extends HibernateGormDatastoreSpec { - MultiTenantFilterBinder filterBinder = new MultiTenantFilterBinder() + GrailsPropertyResolver grailsPropertyResolver = Mock(GrailsPropertyResolver) + MultiTenantFilterBinder filterBinder = new MultiTenantFilterBinder(grailsPropertyResolver) DefaultColumnNameFetcher fetcher = Mock(DefaultColumnNameFetcher) InFlightMetadataCollector mockCollector = GroovyMock(InFlightMetadataCollector) @@ -45,6 +46,7 @@ class MultiTenantFilterBinderSpec extends HibernateGormDatastoreSpec { entity.isMultiTenant() >> true entity.getTenantId() >> tenantId tenantId.getName() >> "tenantId" + grailsPropertyResolver.getProperty(persistentClass, "tenantId") >> property property.setValue(value) persistentClass.setTable(table) @@ -85,6 +87,7 @@ class MultiTenantFilterBinderSpec extends HibernateGormDatastoreSpec { entity.isMultiTenant() >> true entity.getTenantId() >> tenantId tenantId.getName() >> "tenantId" + grailsPropertyResolver.getProperty(persistentClass, "tenantId") >> property // Add stub here entity.isTablePerHierarchySubclass() >> true mockCollector.getFilterDefinition(_) >> Mock(FilterDefinition) @@ -122,6 +125,7 @@ class MultiTenantFilterBinderSpec extends HibernateGormDatastoreSpec { entity.isMultiTenant() >> true entity.getTenantId() >> tenantId tenantId.getName() >> "tenantId" + grailsPropertyResolver.getProperty(persistentClass, "tenantId") >> property // Add stub here mockCollector.getFilterDefinition(_) >> Mock(FilterDefinition) @@ -154,6 +158,7 @@ class MultiTenantFilterBinderSpec extends HibernateGormDatastoreSpec { entity.isMultiTenant() >> true entity.getTenantId() >> tenantId tenantId.getName() >> "tenantId" + grailsPropertyResolver.getProperty(persistentClass, "tenantId") >> property // Add stub here entity.isTablePerHierarchySubclass() >> false mockCollector.getFilterDefinition(_) >> Mock(FilterDefinition)
