This is an automated email from the ASF dual-hosted git repository. borinquenkid pushed a commit to branch feat/gorm-registry-core in repository https://gitbox.apache.org/repos/asf/grails-core.git
commit ed56cf7578d6b0ddd56d80dba66d2c2675184b78 Author: Walter Duque de Estrada <[email protected]> AuthorDate: Sat Jun 27 09:42:04 2026 -0500 fix: mode-aware tenant lookup and bound DatastoreResolvers in GormRegistry findStaticApi/InstanceApi/ValidationApi: the tenant-lookup at priority-2 only checked CurrentTenantHolder. In DATABASE and SCHEMA modes the tenant ID is never stored there explicitly — it comes from the TenantResolver (e.g. a subdomain or system-property resolver). Consult the resolver for those strict modes so that per-tenant child APIs are selected correctly even when no tenant has been bound via Tenants.withId(). Guard with TenantNotFoundException propagation so missing tenants surface as errors rather than silently falling back to the default API. Also skip the API redirect when tenantId equals 'default' to avoid self-loops. createStaticApi / createInstanceApi / createValidationApi: replace the caller- supplied DatastoreResolver with a bound lambda that always returns the specific Datastore captured at registration time. The old resolver was evaluated lazily at call time and could invoke tenant-resolution logic before any tenant context was active, causing spurious TenantNotFoundException during bootstrapping. Co-Authored-By: Claude Sonnet 4.6 <[email protected]> --- .../org/grails/datastore/gorm/GormRegistry.groovy | 74 +++++++++++++++++----- 1 file changed, 59 insertions(+), 15 deletions(-) diff --git a/grails-datamapping-core/src/main/groovy/org/grails/datastore/gorm/GormRegistry.groovy b/grails-datamapping-core/src/main/groovy/org/grails/datastore/gorm/GormRegistry.groovy index 14da3f82c4..d833db021d 100644 --- a/grails-datamapping-core/src/main/groovy/org/grails/datastore/gorm/GormRegistry.groovy +++ b/grails-datamapping-core/src/main/groovy/org/grails/datastore/gorm/GormRegistry.groovy @@ -35,6 +35,8 @@ import org.grails.datastore.mapping.core.connections.MultipleConnectionSourceCap import org.grails.datastore.mapping.model.MappingContext import org.grails.datastore.mapping.model.PersistentEntity import org.grails.datastore.mapping.multitenancy.MultiTenantCapableDatastore +import org.grails.datastore.mapping.multitenancy.MultiTenancySettings +import org.grails.datastore.mapping.multitenancy.exceptions.TenantNotFoundException import org.grails.datastore.mapping.reflect.NameUtils import org.grails.datastore.mapping.transactions.TransactionCapableDatastore @@ -461,13 +463,24 @@ class GormRegistry { // Priority 2: Check current bound tenant if using default qualifier Datastore ds = getDatastoreDirect(normalizedClassName, normalizedQualifier) if (ds instanceof MultiTenantCapableDatastore) { - Serializable tenantId = CurrentTenantHolder.get((MultiTenantCapableDatastore) ds) - if (tenantId != null) { + MultiTenantCapableDatastore mtds = (MultiTenantCapableDatastore) ds + MultiTenancySettings.MultiTenancyMode mode = mtds.getMultiTenancyMode() + boolean strictMode = mode == MultiTenancySettings.MultiTenancyMode.DATABASE || + mode == MultiTenancySettings.MultiTenancyMode.SCHEMA + Serializable tenantId = CurrentTenantHolder.get(mtds) + if (tenantId == null && strictMode) { + try { + tenantId = mtds.tenantResolver.resolveTenantIdentifier() + } catch (TenantNotFoundException e) { + throw e + } + } + if (tenantId != null && !ConnectionSource.DEFAULT.equals(tenantId.toString())) { GormStaticApi api = staticApiRegistry.getDirect(normalizedClassName, tenantId.toString()) if (api != null) return api } } - + // Priority 3: Fall back to default API instance if specialized one not found, // but keep the qualifier so the API can handle tenant binding if (!ConnectionSource.DEFAULT.equals(normalizedQualifier)) { @@ -495,13 +508,24 @@ class GormRegistry { Datastore ds = getDatastoreDirect(normalizedClassName, normalizedQualifier) if (ds instanceof MultiTenantCapableDatastore) { - Serializable tenantId = CurrentTenantHolder.get((MultiTenantCapableDatastore) ds) - if (tenantId != null) { + MultiTenantCapableDatastore mtds = (MultiTenantCapableDatastore) ds + MultiTenancySettings.MultiTenancyMode mode = mtds.getMultiTenancyMode() + boolean strictMode = mode == MultiTenancySettings.MultiTenancyMode.DATABASE || + mode == MultiTenancySettings.MultiTenancyMode.SCHEMA + Serializable tenantId = CurrentTenantHolder.get(mtds) + if (tenantId == null && strictMode) { + try { + tenantId = mtds.tenantResolver.resolveTenantIdentifier() + } catch (TenantNotFoundException e) { + throw e + } + } + if (tenantId != null && !ConnectionSource.DEFAULT.equals(tenantId.toString())) { GormInstanceApi api = instanceApiRegistry.getDirect(normalizedClassName, tenantId.toString()) if (api != null) return api } } - + if (!ConnectionSource.DEFAULT.equals(normalizedQualifier)) { GormInstanceApi api = instanceApiRegistry.getDirect(normalizedClassName, ConnectionSource.DEFAULT) if (api != null) return api @@ -535,13 +559,24 @@ class GormRegistry { Datastore ds = getDatastoreDirect(normalizedClassName, normalizedQualifier) if (ds instanceof MultiTenantCapableDatastore) { - Serializable tenantId = CurrentTenantHolder.get((MultiTenantCapableDatastore) ds) - if (tenantId != null) { + MultiTenantCapableDatastore mtds = (MultiTenantCapableDatastore) ds + MultiTenancySettings.MultiTenancyMode mode = mtds.getMultiTenancyMode() + boolean strictMode = mode == MultiTenancySettings.MultiTenancyMode.DATABASE || + mode == MultiTenancySettings.MultiTenancyMode.SCHEMA + Serializable tenantId = CurrentTenantHolder.get(mtds) + if (tenantId == null && strictMode) { + try { + tenantId = mtds.tenantResolver.resolveTenantIdentifier() + } catch (TenantNotFoundException e) { + throw e + } + } + if (tenantId != null && !ConnectionSource.DEFAULT.equals(tenantId.toString())) { GormValidationApi api = validationApiRegistry.getDirect(normalizedClassName, tenantId.toString()) if (api != null) return api } } - + if (!ConnectionSource.DEFAULT.equals(normalizedQualifier)) { GormValidationApi api = validationApiRegistry.getDirect(normalizedClassName, ConnectionSource.DEFAULT) if (api != null) return api @@ -787,24 +822,33 @@ class GormRegistry { } /** - * Create a GormStaticApi instance + * Create a GormStaticApi instance. + * Uses a bound resolver (always returns the given datastore) at registration time so that + * tenant-resolving DatastoreResolvers are not invoked before any tenant context is active. */ GormStaticApi createStaticApi(Class cls, Datastore datastore, DatastoreResolver resolver, String qualifier) { - return getApiFactory(datastore).createStaticApi(cls, datastore.mappingContext, resolver, qualifier, this) + DatastoreResolver boundResolver = { datastore } as DatastoreResolver + return getApiFactory(datastore).createStaticApi(cls, datastore.mappingContext, boundResolver, qualifier, this) } /** - * Create a GormInstanceApi instance + * Create a GormInstanceApi instance. + * Uses a bound resolver (always returns the given datastore) at registration time so that + * tenant-resolving DatastoreResolvers are not invoked before any tenant context is active. */ GormInstanceApi createInstanceApi(Class cls, Datastore datastore, DatastoreResolver resolver, boolean failOnError, boolean markDirty) { - return getApiFactory(datastore).createInstanceApi(cls, datastore.mappingContext, resolver, this, failOnError, markDirty) + DatastoreResolver boundResolver = { datastore } as DatastoreResolver + return getApiFactory(datastore).createInstanceApi(cls, datastore.mappingContext, boundResolver, this, failOnError, markDirty) } /** - * Create a GormValidationApi instance + * Create a GormValidationApi instance. + * Uses a bound resolver (always returns the given datastore) at registration time so that + * tenant-resolving DatastoreResolvers are not invoked before any tenant context is active. */ GormValidationApi createValidationApi(Class cls, Datastore datastore, DatastoreResolver resolver) { - return getApiFactory(datastore).createValidationApi(cls, datastore.mappingContext, resolver, this) + DatastoreResolver boundResolver = { datastore } as DatastoreResolver + return getApiFactory(datastore).createValidationApi(cls, datastore.mappingContext, boundResolver, this) } /**
