I am using grails 4.0.3 and the app is multi tenant in database level. I am resolving tenant based on subdomain of the request. The multi tenancy is working well for all the cases except for the scheduled jobs. My understanding is since the scheduled job do not have a request scope the tenant resolver is never called because there is no request to trigger the tenant resolver. I have a database transactions going on inside scheduled jobs. And these transactions always point to default database.
How can i resolve a tenant from such scenarios where there is no request scope.
My application.yml configuration for tenant resolver is as follows:
grails:
profile: web
codegen:
defaultPackage: com.pomco.middleware
gorm:
reactor:
# Whether to translate GORM events into Reactor events
# Disabled by default for performance reasons
events: false
multiTenancy:
mode: DATABASE
tenantResolverClass: com.pomco.middleware.multitenant.CustomSubDomainTenantResolver
And the CustomSubDomainTenantResolver looks like this
class CustomSubDomainTenantResolver implements TenantResolver{
@Override
Serializable resolveTenantIdentifier() {
RequestAttributes requestAttributes = RequestContextHolder.getRequestAttributes()
if(requestAttributes instanceof ServletWebRequest) {
HttpServletRequest httpServletRequest = ((ServletWebRequest) requestAttributes).getRequest()
def env = DatasourceEnvironment.lookupEnvironmentByHostname(httpServletRequest.getServerName())
String subDomainId = env?.tenantId
if( subDomainId) {
if(!DatabaseProvisioningService.getSourceByTenantId(subDomainId) && subDomainId != 'DEFAULT'){
def tenantByUrl = DatabaseProvisioningService.tenantSources.find {it.value == DatabaseProvisioningService.getDbUrlByEnv(env)}
if (tenantByUrl){
return tenantByUrl.key
}
}
return subDomainId
}
else {
return ConnectionSource.DEFAULT
}
}
else if(!requestAttributes)
return ConnectionSource.DEFAULT
throw new TenantNotFoundException("Tenant could not be resolved outside a web request")
}
}
The solution for this problem is using tenant manually by using withId
closure inside scheduled job.
For example i was not able to get tenant in following Scheduled job.
@CurrentTenant
def ExampleService{
// this method runs with required tenant as this is a service method annotated with @CurrentTenant
def methodThatHasTenantScope(){
ScheduledFuture<?> helperFuture = null
def result = null
ScheduledFuture<?> future = ses.scheduleWithFixedDelay(new Runnable() {
// BUT here there is no tenant scope as this is a threaded task which doesn't have a request scope
// so this job runs with default tenant
private long count = 0
@Override
void run() {
// job task here runs with default tenant
}
}, DELAY, FREQUENCY_MILLIS, TimeUnit.MILLISECONDS)
return result
}
}
Now we need to get the tenant id manually in the method methodThatHasTenantScope
and pass it to the scheduled job method as follows:
@CurrentTenant
def ExampleService{
@Autowired
HibernateDatastore hibernateDatastore
def methodThatHasTenantScope(){
Serializable tenantId = Tenants.currentId(HibernateDatastore)
log.trace("Current tenant id: $tenantId")
ScheduledFuture<?> helperFuture = null
def result = null
ScheduledFuture<?> future = ses.scheduleWithFixedDelay(new Runnable() {
private long count = 0
@Override
void run() {
//here we have tenantId from the scope of the method
withId(tenantId){
// job task runs with required tenant
}
}
}, DELAY, FREQUENCY_MILLIS, TimeUnit.MILLISECONDS)
return result
}
}