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)
     }
 
     /**

Reply via email to