This is an automated email from the ASF dual-hosted git repository. borinquenkid pushed a commit to branch 8.0.x-hibernate7-dev in repository https://gitbox.apache.org/repos/asf/grails-core.git
commit ec1ca22399fc798b48eafe9de0c88ef2a631e2d2 Author: Walter Duque de Estrada <[email protected]> AuthorDate: Tue Mar 3 10:37:50 2026 -0600 refactor: extract bindOrderBy and applyMultiTenantFilter from CollectionSecondPassBinder --- .../cfg/domainbinding/binder/CollectionBinder.java | 5 +- .../CollectionMultiTenantFilterBinder.java | 65 +++++++++ .../secondpass/CollectionSecondPassBinder.java | 38 +----- .../CollectionMultiTenantFilterBinderSpec.groovy | 149 +++++++++++++++++++++ .../CollectionSecondPassBinderSpec.groovy | 2 +- 5 files changed, 223 insertions(+), 36 deletions(-) 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 93648444f9..174e31d7da 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 @@ -30,6 +30,7 @@ import org.grails.orm.hibernate.cfg.domainbinding.hibernate.HibernatePersistentP import org.grails.orm.hibernate.cfg.domainbinding.hibernate.HibernateToManyProperty; import org.grails.orm.hibernate.cfg.domainbinding.secondpass.BidirectionalOneToManyLinker; import org.grails.orm.hibernate.cfg.domainbinding.secondpass.CollectionKeyColumnUpdater; +import org.grails.orm.hibernate.cfg.domainbinding.secondpass.CollectionMultiTenantFilterBinder; import org.grails.orm.hibernate.cfg.domainbinding.secondpass.CollectionOrderByBinder; import org.grails.orm.hibernate.cfg.domainbinding.secondpass.CollectionSecondPassBinder; import org.grails.orm.hibernate.cfg.domainbinding.secondpass.CollectionWithJoinTableBinder; @@ -114,9 +115,9 @@ public class CollectionBinder { new UnidirectionalOneToManyBinder(collectionWithJoinTableBinder, mappings), collectionWithJoinTableBinder, collectionForPropertyConfigBinder, - new DefaultColumnNameFetcher(namingStrategy), simpleValueColumnBinder, - new CollectionOrderByBinder()); + new CollectionOrderByBinder(), + new CollectionMultiTenantFilterBinder(new DefaultColumnNameFetcher(namingStrategy))); this.listSecondPassBinder = new ListSecondPassBinder( metadataBuildingContext, diff --git a/grails-data-hibernate7/core/src/main/groovy/org/grails/orm/hibernate/cfg/domainbinding/secondpass/CollectionMultiTenantFilterBinder.java b/grails-data-hibernate7/core/src/main/groovy/org/grails/orm/hibernate/cfg/domainbinding/secondpass/CollectionMultiTenantFilterBinder.java new file mode 100644 index 0000000000..f228053dce --- /dev/null +++ b/grails-data-hibernate7/core/src/main/groovy/org/grails/orm/hibernate/cfg/domainbinding/secondpass/CollectionMultiTenantFilterBinder.java @@ -0,0 +1,65 @@ +/* + * 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.secondpass; + +import java.util.Collections; +import java.util.Optional; +import org.grails.datastore.mapping.model.config.GormProperties; +import org.grails.orm.hibernate.cfg.domainbinding.hibernate.HibernateManyToManyProperty; +import org.grails.orm.hibernate.cfg.domainbinding.hibernate.HibernateToManyProperty; +import org.grails.orm.hibernate.cfg.domainbinding.util.DefaultColumnNameFetcher; +import org.hibernate.mapping.Collection; + +/** Applies multi-tenant filters to a collection based on the associated entity's tenancy. */ +public class CollectionMultiTenantFilterBinder { + + private final DefaultColumnNameFetcher defaultColumnNameFetcher; + + /** Creates a new {@link CollectionMultiTenantFilterBinder} instance. */ + public CollectionMultiTenantFilterBinder(DefaultColumnNameFetcher defaultColumnNameFetcher) { + this.defaultColumnNameFetcher = defaultColumnNameFetcher; + } + + /** Applies the multi-tenant filter to the collection if the associated entity is multi-tenant. */ + public void bind(HibernateToManyProperty property, Collection collection) { + Optional.ofNullable(property.getHibernateAssociatedEntity()) + .filter( + referenced -> + !(property instanceof HibernateManyToManyProperty) && referenced.isMultiTenant()) + .map(referenced -> referenced.getMultiTenantFilterCondition(defaultColumnNameFetcher)) + .ifPresent( + filterCondition -> { + if (property.isUnidirectionalOneToMany()) { + collection.addManyToManyFilter( + GormProperties.TENANT_IDENTITY, + filterCondition, + true, + Collections.emptyMap(), + Collections.emptyMap()); + } else { + collection.addFilter( + GormProperties.TENANT_IDENTITY, + filterCondition, + true, + Collections.emptyMap(), + Collections.emptyMap()); + } + }); + } +} 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 83410c5573..69356c87f2 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 @@ -23,7 +23,6 @@ import static org.grails.orm.hibernate.cfg.domainbinding.binder.GrailsDomainBind import jakarta.annotation.Nonnull; import java.util.*; import java.util.Map; -import org.grails.datastore.mapping.model.config.GormProperties; import org.grails.orm.hibernate.cfg.domainbinding.binder.CollectionForPropertyConfigBinder; import org.grails.orm.hibernate.cfg.domainbinding.binder.ManyToOneBinder; import org.grails.orm.hibernate.cfg.domainbinding.binder.SimpleValueColumnBinder; @@ -31,7 +30,6 @@ import org.grails.orm.hibernate.cfg.domainbinding.hibernate.HibernateManyToManyP import org.grails.orm.hibernate.cfg.domainbinding.hibernate.HibernateOneToManyProperty; import org.grails.orm.hibernate.cfg.domainbinding.hibernate.HibernateToManyProperty; import org.grails.orm.hibernate.cfg.domainbinding.hibernate.HibernateManyToOneProperty; -import org.grails.orm.hibernate.cfg.domainbinding.util.DefaultColumnNameFetcher; import org.hibernate.MappingException; import org.hibernate.boot.spi.InFlightMetadataCollector; import org.hibernate.mapping.*; @@ -45,8 +43,8 @@ public class CollectionSecondPassBinder { private static final Logger LOG = LoggerFactory.getLogger(CollectionSecondPassBinder.class); - private final DefaultColumnNameFetcher defaultColumnNameFetcher; private final CollectionOrderByBinder collectionOrderByBinder; + private final CollectionMultiTenantFilterBinder collectionMultiTenantFilterBinder; private final ManyToOneBinder manyToOneBinder; private final PrimaryKeyValueCreator primaryKeyValueCreator; private final CollectionKeyColumnUpdater collectionKeyColumnUpdater; @@ -67,9 +65,9 @@ public class CollectionSecondPassBinder { UnidirectionalOneToManyBinder unidirectionalOneToManyBinder, CollectionWithJoinTableBinder collectionWithJoinTableBinder, CollectionForPropertyConfigBinder collectionForPropertyConfigBinder, - DefaultColumnNameFetcher defaultColumnNameFetcher, SimpleValueColumnBinder simpleValueColumnBinder, - CollectionOrderByBinder collectionOrderByBinder) { + CollectionOrderByBinder collectionOrderByBinder, + CollectionMultiTenantFilterBinder collectionMultiTenantFilterBinder) { this.manyToOneBinder = manyToOneBinder; this.primaryKeyValueCreator = primaryKeyValueCreator; this.collectionKeyColumnUpdater = collectionKeyColumnUpdater; @@ -78,9 +76,9 @@ public class CollectionSecondPassBinder { this.unidirectionalOneToManyBinder = unidirectionalOneToManyBinder; this.collectionWithJoinTableBinder = collectionWithJoinTableBinder; this.collectionForPropertyConfigBinder = collectionForPropertyConfigBinder; - this.defaultColumnNameFetcher = defaultColumnNameFetcher; this.simpleValueColumnBinder = simpleValueColumnBinder; this.collectionOrderByBinder = collectionOrderByBinder; + this.collectionMultiTenantFilterBinder = collectionMultiTenantFilterBinder; } /** Bind collection second pass. */ @@ -94,8 +92,7 @@ public class CollectionSecondPassBinder { collectionOrderByBinder.bind(property, collection, associatedClass); bindOneToManyAssociation(property, associatedClass, collection); - applyMultiTenantFilter(property, collection); - + collectionMultiTenantFilterBinder.bind(property, collection); if (property.isSorted()) { collection.setSorted(true); } @@ -126,31 +123,6 @@ public class CollectionSecondPassBinder { collectionForPropertyConfigBinder.bindCollectionForPropertyConfig(collection, property); } - private void applyMultiTenantFilter(HibernateToManyProperty property, Collection collection) { - Optional.ofNullable(property.getHibernateAssociatedEntity()) - .filter( - referenced -> - !(property instanceof HibernateManyToManyProperty) && referenced.isMultiTenant()) - .map(referenced -> referenced.getMultiTenantFilterCondition(defaultColumnNameFetcher)) - .ifPresent( - filterCondition -> { - if (property.isUnidirectionalOneToMany()) { - collection.addManyToManyFilter( - GormProperties.TENANT_IDENTITY, - filterCondition, - true, - Collections.emptyMap(), - Collections.emptyMap()); - } else { - collection.addFilter( - GormProperties.TENANT_IDENTITY, - filterCondition, - true, - Collections.emptyMap(), - Collections.emptyMap()); - } - }); - } private void bindCollectionKey( HibernateToManyProperty property, diff --git a/grails-data-hibernate7/core/src/test/groovy/org/grails/orm/hibernate/cfg/domainbinding/secondpass/CollectionMultiTenantFilterBinderSpec.groovy b/grails-data-hibernate7/core/src/test/groovy/org/grails/orm/hibernate/cfg/domainbinding/secondpass/CollectionMultiTenantFilterBinderSpec.groovy new file mode 100644 index 0000000000..7442b848db --- /dev/null +++ b/grails-data-hibernate7/core/src/test/groovy/org/grails/orm/hibernate/cfg/domainbinding/secondpass/CollectionMultiTenantFilterBinderSpec.groovy @@ -0,0 +1,149 @@ +package org.grails.orm.hibernate.cfg.domainbinding.secondpass + +import grails.gorm.MultiTenant +import grails.gorm.annotation.Entity +import grails.gorm.specs.HibernateGormDatastoreSpec +import org.grails.datastore.mapping.model.config.GormProperties +import org.grails.datastore.mapping.multitenancy.MultiTenancySettings +import org.grails.datastore.mapping.multitenancy.resolvers.SystemPropertyTenantResolver +import org.grails.orm.hibernate.cfg.domainbinding.hibernate.GrailsHibernatePersistentEntity +import org.grails.orm.hibernate.cfg.domainbinding.hibernate.HibernateToManyProperty +import org.grails.orm.hibernate.cfg.domainbinding.util.BackticksRemover +import org.grails.orm.hibernate.cfg.domainbinding.util.DefaultColumnNameFetcher +import org.hibernate.mapping.Bag +import spock.lang.Subject + +class CollectionMultiTenantFilterBinderSpec extends HibernateGormDatastoreSpec { + + @Subject + CollectionMultiTenantFilterBinder binder + + void setupSpec() { + manager.addAllDomainClasses([ + CMTBBidirectionalOwner, + CMTBBidirectionalItem, + CMTBUnidirectionalOwner, + CMTBUnidirectionalItem, + CMTBNonTenantOwner, + CMTBNonTenantItem, + CMTBManyToManyOwner, + CMTBManyToManyItem, + ]) + manager.grailsConfig = [ + "grails.gorm.multiTenancy.mode" : MultiTenancySettings.MultiTenancyMode.DISCRIMINATOR, + "grails.gorm.multiTenancy.tenantResolverClass": SystemPropertyTenantResolver, + ] + } + + void setup() { + def ns = getGrailsDomainBinder().getNamingStrategy() + binder = new CollectionMultiTenantFilterBinder(new DefaultColumnNameFetcher(ns, new BackticksRemover())) + } + + private HibernateToManyProperty propertyFor(Class ownerClass, String name = "items") { + (getPersistentEntity(ownerClass) as GrailsHibernatePersistentEntity).getPropertyByName(name) as HibernateToManyProperty + } + + def "bind adds collection filter for bidirectional one-to-many to multi-tenant entity"() { + given: + def property = propertyFor(CMTBBidirectionalOwner) + def collection = new Bag(getGrailsDomainBinder().getMetadataBuildingContext(), null) + + when: + binder.bind(property, collection) + + then: + collection.getFilters().any { it.getName() == GormProperties.TENANT_IDENTITY } + collection.getManyToManyFilters().isEmpty() + } + + def "bind adds manyToMany filter for unidirectional one-to-many to multi-tenant entity"() { + given: + def property = propertyFor(CMTBUnidirectionalOwner) + def collection = new Bag(getGrailsDomainBinder().getMetadataBuildingContext(), null) + + when: + binder.bind(property, collection) + + then: + collection.getManyToManyFilters().any { it.getName() == GormProperties.TENANT_IDENTITY } + collection.getFilters().isEmpty() + } + + def "bind does not add filter for ManyToMany even when associated entity is multi-tenant"() { + given: + def property = propertyFor(CMTBManyToManyOwner) + def collection = new Bag(getGrailsDomainBinder().getMetadataBuildingContext(), null) + + when: + binder.bind(property, collection) + + then: + collection.getFilters().isEmpty() + collection.getManyToManyFilters().isEmpty() + } + + def "bind does not add filter when associated entity is not multi-tenant"() { + given: + def property = propertyFor(CMTBNonTenantOwner) + def collection = new Bag(getGrailsDomainBinder().getMetadataBuildingContext(), null) + + when: + binder.bind(property, collection) + + then: + collection.getFilters().isEmpty() + collection.getManyToManyFilters().isEmpty() + } +} + +@Entity +class CMTBBidirectionalOwner { + Long id + static hasMany = [items: CMTBBidirectionalItem] +} + +@Entity +class CMTBBidirectionalItem implements MultiTenant<CMTBBidirectionalItem> { + Long id + Long tenantId + CMTBBidirectionalOwner owner + static belongsTo = [owner: CMTBBidirectionalOwner] +} + +@Entity +class CMTBUnidirectionalOwner { + Long id + static hasMany = [items: CMTBUnidirectionalItem] +} + +@Entity +class CMTBUnidirectionalItem implements MultiTenant<CMTBUnidirectionalItem> { + Long id + Long tenantId +} + +@Entity +class CMTBNonTenantOwner { + Long id + static hasMany = [items: CMTBNonTenantItem] +} + +@Entity +class CMTBNonTenantItem { + Long id + String name +} + +@Entity +class CMTBManyToManyOwner { + Long id + static hasMany = [items: CMTBManyToManyItem] +} + +@Entity +class CMTBManyToManyItem implements MultiTenant<CMTBManyToManyItem> { + Long id + Long tenantId + static hasMany = [owners: CMTBManyToManyOwner] +} diff --git a/grails-data-hibernate7/core/src/test/groovy/org/grails/orm/hibernate/cfg/domainbinding/secondpass/CollectionSecondPassBinderSpec.groovy b/grails-data-hibernate7/core/src/test/groovy/org/grails/orm/hibernate/cfg/domainbinding/secondpass/CollectionSecondPassBinderSpec.groovy index 09f56705c9..bce105641c 100644 --- a/grails-data-hibernate7/core/src/test/groovy/org/grails/orm/hibernate/cfg/domainbinding/secondpass/CollectionSecondPassBinderSpec.groovy +++ b/grails-data-hibernate7/core/src/test/groovy/org/grails/orm/hibernate/cfg/domainbinding/secondpass/CollectionSecondPassBinderSpec.groovy @@ -41,7 +41,7 @@ class CollectionSecondPassBinderSpec extends HibernateGormDatastoreSpec { def dcnf = new DefaultColumnNameFetcher(ns, new BackticksRemover()) def svcb = new SimpleValueColumnBinder() - binder = new CollectionSecondPassBinder(mtob, pkvc, cku, botml, dkvb, uotmb, cwjtb, cfpcb, dcnf, svcb, new CollectionOrderByBinder()) + binder = new CollectionSecondPassBinder(mtob, pkvc, cku, botml, dkvb, uotmb, cwjtb, cfpcb, svcb, new CollectionOrderByBinder(), new CollectionMultiTenantFilterBinder(dcnf)) } protected HibernatePersistentProperty createTestHibernateToManyProperty(Class<?> domainClass = CSPBTestEntityWithMany, String propertyName = "items") {
