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 20762d8e0db9d93d375a0f10eecef313291e9e2e Author: Walter Duque de Estrada <[email protected]> AuthorDate: Sat Feb 14 22:13:17 2026 -0600 Refactor multi-tenant filter binding to dedicated class - Extract filter logic to MultiTenantFilterBinder for better modularity. - Improve shouldApplyFilter for Hibernate 7 class and proxy compatibility. - Update tests and dependencies to support Hibernate 7 mapping models. --- grails-data-hibernate7/core/build.gradle | 1 + .../orm/hibernate/cfg/GrailsDomainBinder.java | 78 ++-------- .../util/MultiTenantFilterBinder.java | 117 ++++++++++++++ .../util/MultiTenantFilterBinderSpec.groovy | 168 +++++++++++++++++++++ 4 files changed, 299 insertions(+), 65 deletions(-) diff --git a/grails-data-hibernate7/core/build.gradle b/grails-data-hibernate7/core/build.gradle index 9ba23bd2bb..5bf342dcf8 100644 --- a/grails-data-hibernate7/core/build.gradle +++ b/grails-data-hibernate7/core/build.gradle @@ -56,6 +56,7 @@ dependencies { exclude group:'org.slf4j', module:'slf4j-log4j12' exclude group:'xml-apis', module:'xml-apis' } + api "org.hibernate.models:hibernate-models:1.0.0" api 'org.hibernate.validator:hibernate-validator', { exclude group:'commons-logging', module:'commons-logging' exclude group:'commons-collections', module:'commons-collections' 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 e1588603a8..6392fd1a42 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 @@ -45,6 +45,7 @@ import org.grails.orm.hibernate.cfg.domainbinding.collectionType.CollectionHolde import org.grails.orm.hibernate.cfg.domainbinding.binder.CollectionBinder; import org.grails.orm.hibernate.cfg.domainbinding.util.ColumnNameForPropertyAndPathFetcher; import org.grails.orm.hibernate.cfg.domainbinding.util.DefaultColumnNameFetcher; +import org.grails.orm.hibernate.cfg.domainbinding.util.MultiTenantFilterBinder; import org.grails.orm.hibernate.cfg.domainbinding.util.BackticksRemover; import org.grails.orm.hibernate.cfg.domainbinding.util.ForeignKeyColumnCountCalculator; import org.grails.orm.hibernate.cfg.domainbinding.util.NamingStrategyWrapper; @@ -226,12 +227,13 @@ public class GrailsDomainBinder SimpleIdBinder simpleIdBinder = new SimpleIdBinder(metadataBuildingContext, namingStrategy, jdbcEnvironment, new BasicValueIdCreator(jdbcEnvironment), simpleValueBinder, propertyBinder); IdentityBinder identityBinder = new IdentityBinder(simpleIdBinder, compositeIdBinder); VersionBinder versionBinder = new VersionBinder(metadataBuildingContext, simpleValueBinder, propertyBinder, BasicValue::new); + MultiTenantFilterBinder multiTenantFilterBinder = new MultiTenantFilterBinder(); hibernateMappingContext .getHibernatePersistentEntities(dataSourceName) .stream() .filter(persistentEntity -> persistentEntity.forGrailsDomainMapping(dataSourceName)) - .forEach(hibernatePersistentEntity -> bindRoot(hibernatePersistentEntity, metadataCollector, sessionFactoryName, defaultColumnNameFetcher, columnNameForPropertyAndPathFetcher, identityBinder, versionBinder, grailsPropertyBinder, classBinder, propertyFromValueCreator)); + .forEach(hibernatePersistentEntity -> bindRoot(hibernatePersistentEntity, metadataCollector, sessionFactoryName, defaultColumnNameFetcher, columnNameForPropertyAndPathFetcher, identityBinder, versionBinder, grailsPropertyBinder, classBinder, propertyFromValueCreator, multiTenantFilterBinder)); } @@ -262,7 +264,7 @@ public class GrailsDomainBinder * @param mappings The Hibernate Mappings object * @param sessionFactoryBeanName the session factory bean name */ - protected void bindRoot(@Nonnull GrailsHibernatePersistentEntity entity,@Nonnull InFlightMetadataCollector mappings, String sessionFactoryBeanName, DefaultColumnNameFetcher defaultColumnNameFetcher, ColumnNameForPropertyAndPathFetcher columnNameForPropertyAndPathFetcher, IdentityBinder identityBinder, VersionBinder versionBinder, GrailsPropertyBinder grailsPropertyBinder, ClassBinder classBinder, PropertyFromValueCreator propertyFromValueCreator) { + protected void bindRoot(@Nonnull GrailsHibernatePersistentEntity entity,@Nonnull InFlightMetadataCollector mappings, String sessionFactoryBeanName, DefaultColumnNameFetcher defaultColumnNameFetcher, ColumnNameForPropertyAndPathFetcher columnNameForPropertyAndPathFetcher, IdentityBinder identityBinder, VersionBinder versionBinder, GrailsPropertyBinder grailsPropertyBinder, ClassBinder classBinder, PropertyFromValueCreator propertyFromValueCreator, MultiTenantFilterBinder multiTenantFi [...] if (mappings.getEntityBinding(entity.getName()) != null) { LOG.info("[GrailsDomainBinder] Class [" + entity.getName() + "] is already mapped, skipping.. "); return; @@ -279,10 +281,10 @@ public class GrailsDomainBinder bindDiscriminatorProperty(root.getTable(), root, m); } // bind the sub classes - children.forEach(sub -> bindSubClass(sub, root, mappings, sessionFactoryBeanName, finalMapping,mappingCacheHolder, defaultColumnNameFetcher, columnNameForPropertyAndPathFetcher, grailsPropertyBinder, classBinder, propertyFromValueCreator )); + children.forEach(sub -> bindSubClass(sub, root, mappings, sessionFactoryBeanName, finalMapping,mappingCacheHolder, defaultColumnNameFetcher, columnNameForPropertyAndPathFetcher, grailsPropertyBinder, classBinder, propertyFromValueCreator, multiTenantFilterBinder )); } - addMultiTenantFilterIfNecessary(entity, root, mappings, defaultColumnNameFetcher); + multiTenantFilterBinder.addMultiTenantFilterIfNecessary(entity, root, mappings, defaultColumnNameFetcher); mappings.addEntityBinding(root); } @@ -296,47 +298,6 @@ public class GrailsDomainBinder return namingStrategy; } - /** - * Add a Hibernate filter for multitenancy if the persistent class is multitenant - * - * @param entity target persistent entity for get tenant information - * @param persistentClass persistent class for add the filter and get tenant property info - * @param mappings mappings to add the filter - */ - private void addMultiTenantFilterIfNecessary( - @Nonnull GrailsHibernatePersistentEntity entity, PersistentClass persistentClass, - @Nonnull InFlightMetadataCollector mappings, DefaultColumnNameFetcher defaultColumnNameFetcher) { - - if (entity.isMultiTenant()) { - TenantId tenantId = entity.getTenantId(); - - if (tenantId != null) { - String filterCondition = entity.getMultiTenantFilterCondition(defaultColumnNameFetcher); - - persistentClass.addFilter( - GormProperties.TENANT_IDENTITY, - filterCondition, - true, - Collections.emptyMap(), - Collections.emptyMap() - ); - - Property property = getProperty(persistentClass, tenantId.getName()); - if (property.getValue() instanceof BasicValue basicValue) { - JdbcMapping jdbcMapping = basicValue.resolve().getJdbcMapping(); - var stringVMap = Collections.singletonMap(GormProperties.TENANT_IDENTITY, jdbcMapping); - FilterDefinition definition = new FilterDefinition( - GormProperties.TENANT_IDENTITY, - filterCondition, - stringVMap - ); - mappings.addFilterDefinition(definition); - } - } - } - } - - /** * Binds a sub class. * @@ -350,24 +311,24 @@ public class GrailsDomainBinder PersistentClass parent, @Nonnull InFlightMetadataCollector mappings, String sessionFactoryBeanName - , Mapping m, MappingCacheHolder mappingCacheHolder, DefaultColumnNameFetcher defaultColumnNameFetcher, ColumnNameForPropertyAndPathFetcher columnNameForPropertyAndPathFetcher, GrailsPropertyBinder grailsPropertyBinder, ClassBinder classBinder, PropertyFromValueCreator propertyFromValueCreator) { + , Mapping m, MappingCacheHolder mappingCacheHolder, DefaultColumnNameFetcher defaultColumnNameFetcher, ColumnNameForPropertyAndPathFetcher columnNameForPropertyAndPathFetcher, GrailsPropertyBinder grailsPropertyBinder, ClassBinder classBinder, PropertyFromValueCreator propertyFromValueCreator, MultiTenantFilterBinder multiTenantFilterBinder) { mappingCacheHolder.cacheMapping(sub); - Subclass subClass = createSubclassMapping(sub, parent, mappings, sessionFactoryBeanName, m, defaultColumnNameFetcher, columnNameForPropertyAndPathFetcher, grailsPropertyBinder, classBinder, propertyFromValueCreator); + Subclass subClass = createSubclassMapping(sub, parent, mappings, sessionFactoryBeanName, m, defaultColumnNameFetcher, columnNameForPropertyAndPathFetcher, grailsPropertyBinder, classBinder, propertyFromValueCreator, multiTenantFilterBinder); parent.addSubclass(subClass); mappings.addEntityBinding(subClass); - addMultiTenantFilterIfNecessary(sub, subClass, mappings, defaultColumnNameFetcher); + multiTenantFilterBinder.addMultiTenantFilterIfNecessary(sub, subClass, mappings, defaultColumnNameFetcher); var children = sub.getChildEntities(dataSourceName); if (!children.isEmpty()) { // bind the sub classes - children.forEach(sub1 -> bindSubClass(sub1, subClass, mappings, sessionFactoryBeanName, m,mappingCacheHolder, defaultColumnNameFetcher, columnNameForPropertyAndPathFetcher, grailsPropertyBinder, classBinder, propertyFromValueCreator )); + children.forEach(sub1 -> bindSubClass(sub1, subClass, mappings, sessionFactoryBeanName, m,mappingCacheHolder, defaultColumnNameFetcher, columnNameForPropertyAndPathFetcher, grailsPropertyBinder, classBinder, propertyFromValueCreator, multiTenantFilterBinder )); } } - private @NonNull Subclass createSubclassMapping(@NonNull GrailsHibernatePersistentEntity subEntity, PersistentClass parent, @NonNull InFlightMetadataCollector mappings, String sessionFactoryBeanName, Mapping m, DefaultColumnNameFetcher defaultColumnNameFetcher, ColumnNameForPropertyAndPathFetcher columnNameForPropertyAndPathFetcher, GrailsPropertyBinder grailsPropertyBinder, ClassBinder classBinder, PropertyFromValueCreator propertyFromValueCreator) { + private @NonNull Subclass createSubclassMapping(@NonNull GrailsHibernatePersistentEntity subEntity, PersistentClass parent, @NonNull InFlightMetadataCollector mappings, String sessionFactoryBeanName, Mapping m, DefaultColumnNameFetcher defaultColumnNameFetcher, ColumnNameForPropertyAndPathFetcher columnNameForPropertyAndPathFetcher, GrailsPropertyBinder grailsPropertyBinder, ClassBinder classBinder, PropertyFromValueCreator propertyFromValueCreator, MultiTenantFilterBinder multiTenan [...] Subclass subClass; subEntity.configureDerivedProperties(); if (!m.getTablePerHierarchy() && !m.isTablePerConcreteClass()) { @@ -381,7 +342,7 @@ public class GrailsDomainBinder else { subClass = new SingleTableSubclass(parent, this.metadataBuildingContext); subClass.setDiscriminatorValue(subEntity.getDiscriminatorValue()); - bindSubClass(subEntity, subClass, mappings, sessionFactoryBeanName, defaultColumnNameFetcher, grailsPropertyBinder, classBinder, propertyFromValueCreator); + bindSubClass(subEntity, subClass, mappings, sessionFactoryBeanName, defaultColumnNameFetcher, grailsPropertyBinder, classBinder, propertyFromValueCreator, multiTenantFilterBinder); } subClass.setBatchSize(Optional.ofNullable(m.getBatchSize()).orElse(-1)); subClass.setDynamicUpdate(m.getDynamicUpdate()); @@ -480,7 +441,7 @@ public class GrailsDomainBinder * @param mappings The mappings instance */ private void bindSubClass(@Nonnull GrailsHibernatePersistentEntity sub, Subclass subClass, @Nonnull InFlightMetadataCollector mappings, - String sessionFactoryBeanName, DefaultColumnNameFetcher defaultColumnNameFetcher, GrailsPropertyBinder grailsPropertyBinder, ClassBinder classBinder, PropertyFromValueCreator propertyFromValueCreator) { + String sessionFactoryBeanName, DefaultColumnNameFetcher defaultColumnNameFetcher, GrailsPropertyBinder grailsPropertyBinder, ClassBinder classBinder, PropertyFromValueCreator propertyFromValueCreator, MultiTenantFilterBinder multiTenantFilterBinder) { classBinder.bindClass(sub, subClass, mappings); if (LOG.isDebugEnabled()) @@ -653,17 +614,4 @@ public class GrailsDomainBinder public void contribute(TypeContributions typeContributions, ServiceRegistry serviceRegistry) { } - - private 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; - } - } } \ No newline at end of file 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 new file mode 100644 index 0000000000..a74d51f0bb --- /dev/null +++ b/grails-data-hibernate7/core/src/main/groovy/org/grails/orm/hibernate/cfg/domainbinding/util/MultiTenantFilterBinder.java @@ -0,0 +1,117 @@ +package org.grails.orm.hibernate.cfg.domainbinding.util; + +import java.util.Collections; +import java.util.Optional; + +import jakarta.annotation.Nonnull; + +import org.hibernate.MappingException; +import org.hibernate.boot.spi.InFlightMetadataCollector; +import org.hibernate.metamodel.mapping.JdbcMapping; +import org.hibernate.engine.spi.FilterDefinition; +import org.hibernate.mapping.BasicValue; +import org.hibernate.mapping.Component; +import org.hibernate.mapping.PersistentClass; +import org.hibernate.mapping.Property; +import org.hibernate.mapping.RootClass; + +import org.grails.datastore.mapping.model.config.GormProperties; +import org.grails.datastore.mapping.model.types.TenantId; +import org.grails.orm.hibernate.cfg.GrailsHibernatePersistentEntity; + +/** + * Utility class for binding multi-tenant filters to the Hibernate meta model. + * + * @author Walter Duque de Estrada + * @author Graeme Rocher + * @since 7.0 + */ +public class MultiTenantFilterBinder { + + /** + * Adds a multi-tenant filter to the given persistent class if necessary. + * + * @param entity The target persistent entity + * @param persistentClass The persistent class to add the filter to + * @param mappings The in-flight metadata collector + * @param fetcher The column name fetcher + */ + public void addMultiTenantFilterIfNecessary( + @Nonnull GrailsHibernatePersistentEntity entity, + @Nonnull PersistentClass persistentClass, + @Nonnull InFlightMetadataCollector mappings, + @Nonnull DefaultColumnNameFetcher fetcher) { + + if (!entity.isMultiTenant()) { + return; + } + + Optional.ofNullable(entity.getTenantId()) + .map(TenantId::getName) + .map(name -> getProperty(persistentClass, name)) + .ifPresent(property -> { + var filterName = GormProperties.TENANT_IDENTITY; + ensureGlobalFilterDefinition(mappings, filterName, property); + applyFilterToPersistentClass(entity, persistentClass, fetcher, filterName, property); + }); + } + + private void ensureGlobalFilterDefinition(InFlightMetadataCollector mappings, String filterName, Property property) { + if (mappings.getFilterDefinition(filterName) == null && property.getValue() instanceof BasicValue basicValue) { + JdbcMapping jdbcMapping = basicValue.resolve().getJdbcMapping(); + mappings.addFilterDefinition(new FilterDefinition( + filterName, + null, // No default condition; let classes specify their own + Collections.singletonMap(filterName, jdbcMapping) + )); + } + } + + private void applyFilterToPersistentClass( + GrailsHibernatePersistentEntity entity, + PersistentClass persistentClass, + DefaultColumnNameFetcher fetcher, + String filterName, + Property property) { + + if (shouldApplyFilter(entity, persistentClass, property)) { + persistentClass.addFilter( + filterName, + entity.getMultiTenantFilterCondition(fetcher), + true, // autoAliasInjection + Collections.emptyMap(), + Collections.emptyMap() + ); + } + } + + private boolean shouldApplyFilter(GrailsHibernatePersistentEntity entity, PersistentClass persistentClass, Property property) { + boolean isRoot = persistentClass instanceof RootClass || persistentClass.equals(persistentClass.getRootClass()); + + var table = persistentClass.getTable(); + var propertyValue = property.getValue(); + var propertyTable = propertyValue != null ? propertyValue.getTable() : null; + + boolean isInherited = table != null && propertyTable != null && !table.equals(propertyTable); + + // Apply if it's the root or if the subclass has its own table containing the column (UnionSubclass). + // Skip if it's a SingleTable subclass (redundant) or JoinedSubclass where column is in root (alias safety). + if (isRoot || !isInherited) { + return isRoot || !entity.isTablePerHierarchySubclass(); + } + 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 new file mode 100644 index 0000000000..58de04027b --- /dev/null +++ b/grails-data-hibernate7/core/src/test/groovy/org/grails/orm/hibernate/cfg/domainbinding/util/MultiTenantFilterBinderSpec.groovy @@ -0,0 +1,168 @@ +package org.grails.orm.hibernate.cfg.domainbinding.util + +import grails.gorm.specs.HibernateGormDatastoreSpec +import org.grails.orm.hibernate.cfg.domainbinding.util.MultiTenantFilterBinder +import org.grails.orm.hibernate.cfg.domainbinding.util.DefaultColumnNameFetcher +import org.grails.datastore.mapping.model.config.GormProperties +import org.grails.orm.hibernate.cfg.GrailsHibernatePersistentEntity +import org.hibernate.boot.spi.InFlightMetadataCollector +import org.hibernate.mapping.BasicValue +import org.hibernate.mapping.PersistentClass +import org.hibernate.mapping.Property +import org.hibernate.mapping.RootClass +import org.hibernate.mapping.SingleTableSubclass +import org.hibernate.mapping.JoinedSubclass +import org.hibernate.mapping.UnionSubclass +import org.hibernate.mapping.Table +import org.hibernate.metamodel.mapping.JdbcMapping +import org.hibernate.engine.spi.FilterDefinition +import org.grails.datastore.mapping.model.types.TenantId +import spock.lang.Specification + +/** + * Tests for MultiTenantFilterBinder. + */ +class MultiTenantFilterBinderSpec extends HibernateGormDatastoreSpec { + + MultiTenantFilterBinder filterBinder = new MultiTenantFilterBinder() + DefaultColumnNameFetcher fetcher = Mock(DefaultColumnNameFetcher) + InFlightMetadataCollector mockCollector = GroovyMock(InFlightMetadataCollector) + + void "test add multi tenant filter to root class"() { + given: + def entity = Mock(GrailsHibernatePersistentEntity) + def buildingContext = getGrailsDomainBinder().getMetadataBuildingContext() + def persistentClass = new RootClass(buildingContext) + + def tenantId = Mock(TenantId) + def property = new Property() + property.setName("tenantId") + + def table = new Table("ROOT_TABLE") + def value = new BasicValue(buildingContext, table) + value.setTypeName("long") + + entity.isMultiTenant() >> true + entity.getTenantId() >> tenantId + tenantId.getName() >> "tenantId" + + property.setValue(value) + persistentClass.setTable(table) + persistentClass.addProperty(property) + + // Setup for FilterDefinition + mockCollector.getFilterDefinition(GormProperties.TENANT_IDENTITY) >> null + + entity.getMultiTenantFilterCondition(fetcher) >> "tenant_id = :tenantId" + + when: + filterBinder.addMultiTenantFilterIfNecessary(entity, persistentClass, mockCollector, fetcher) + + then: + 1 * mockCollector.addFilterDefinition(_ as FilterDefinition) + persistentClass.getFilters().any { it.getName() == GormProperties.TENANT_IDENTITY && it.getCondition() == "tenant_id = :tenantId" } + } + + void "test skip filter for single table subclass (redundant)"() { + given: + def entity = Mock(GrailsHibernatePersistentEntity) + def buildingContext = getGrailsDomainBinder().getMetadataBuildingContext() + def rootClass = new RootClass(buildingContext) + def table = new Table("ROOT_TABLE") + rootClass.setTable(table) + + def persistentClass = new SingleTableSubclass(rootClass, buildingContext) + def tenantId = Mock(TenantId) + + def property = new Property() + property.setName("tenantId") + def value = new BasicValue(buildingContext, table) + value.setTypeName("long") + property.setValue(value) + + rootClass.addProperty(property) + + entity.isMultiTenant() >> true + entity.getTenantId() >> tenantId + tenantId.getName() >> "tenantId" + + entity.isTablePerHierarchySubclass() >> true + mockCollector.getFilterDefinition(_) >> Mock(FilterDefinition) + + when: + filterBinder.addMultiTenantFilterIfNecessary(entity, persistentClass, mockCollector, fetcher) + + then: + !persistentClass.getFilters().any { it.getName() == GormProperties.TENANT_IDENTITY } + } + + void "test skip filter for joined subclass if inherited (alias safety)"() { + given: + def entity = Mock(GrailsHibernatePersistentEntity) + def buildingContext = getGrailsDomainBinder().getMetadataBuildingContext() + def rootClass = new RootClass(buildingContext) + def rootTable = new Table("ROOT_TABLE") + rootTable.setName("ROOT_TABLE") + rootClass.setTable(rootTable) + + def persistentClass = new JoinedSubclass(rootClass, buildingContext) + def subTable = new Table("SUB_TABLE") + subTable.setName("SUB_TABLE") + persistentClass.setTable(subTable) + + def tenantId = Mock(TenantId) + def property = new Property() + property.setName("tenantId") + def value = new BasicValue(buildingContext, rootTable) + value.setTypeName("long") + property.setValue(value) + + rootClass.addProperty(property) + + entity.isMultiTenant() >> true + entity.getTenantId() >> tenantId + tenantId.getName() >> "tenantId" + + mockCollector.getFilterDefinition(_) >> Mock(FilterDefinition) + + when: + filterBinder.addMultiTenantFilterIfNecessary(entity, persistentClass, mockCollector, fetcher) + + then: + !persistentClass.getFilters().any { it.getName() == GormProperties.TENANT_IDENTITY } + } + + void "test add filter for union subclass (own table)"() { + given: + def entity = Mock(GrailsHibernatePersistentEntity) + def buildingContext = getGrailsDomainBinder().getMetadataBuildingContext() + def rootClass = new RootClass(buildingContext) + def subTable = new Table("SUB_TABLE") + + def persistentClass = new UnionSubclass(rootClass, buildingContext) + persistentClass.setTable(subTable) + + def tenantId = Mock(TenantId) + def property = new Property() + property.setName("tenantId") + def value = new BasicValue(buildingContext, subTable) + value.setTypeName("long") + property.setValue(value) + + persistentClass.addProperty(property) + + entity.isMultiTenant() >> true + entity.getTenantId() >> tenantId + tenantId.getName() >> "tenantId" + + entity.isTablePerHierarchySubclass() >> false + mockCollector.getFilterDefinition(_) >> Mock(FilterDefinition) + entity.getMultiTenantFilterCondition(fetcher) >> "tenant_id = :tenantId" + + when: + filterBinder.addMultiTenantFilterIfNecessary(entity, persistentClass, mockCollector, fetcher) + + then: + persistentClass.getFilters().any { it.getName() == GormProperties.TENANT_IDENTITY && it.getCondition() == "tenant_id = :tenantId" } + } +}
