You are viewing a plain text version of this content. The canonical link for it is here.
Posted to commits@grails.apache.org by bo...@apache.org on 2026/02/17 18:42:25 UTC
(grails-core) 26/26: FEAT: Extract and centralize collection property resolution and configuration
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 <wb...@mac.com>
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)