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)

Reply via email to