Skip to main content
Rate this page

Multitenancy With the Thread Context

What Is a Thread Context?

The SAP Cloud SDK for Java provides a so-called ThreadContext. It serves as thread-safe storage for potentially sensitive information. Specifically, the following three objects are stored:

  • The current Tenant
  • The current Principal (User)
  • The JSON Web Token (JWT)

This information is used throughout the SAP Cloud SDK to provide features like tenant and principal isolation, JWT verification and authorization against other systems and services. To ensure different tenants and users are properly isolated in an application, this information is always limited to the thread it was created on unless it is explicitly passed on by the application (see Propagating the Thread Context).

info

Multi-tenancy describes the access of different, technically separated user groups to the same instance of an application. In the terms of XSUAA service, these user groups are called Tenants, while in terms of Identity Authentication Service (IAS) they are called Zones.

The SAP Cloud SDK for Java uses the term Tenant to refer to both XSUAA Tenants and IAS Zones. This implies, that the tenantId exposed in the Tenant interface either returns the tenantId or zoneId, depending on the context you are currently running in.

How Is a Thread Context Created?

The SAP Cloud SDK provides a RequestFilter that will listen to incoming HTTP requests. If the Authorization header contains a JWT from the AppRouter, the filter will:

  • Verify this token
  • Store it in the ThreadContext and
  • Pull the Tenant and Principal information from it

How Can the Thread Context Be Used?

Accessing Information

The Thread context can be accessed via the static ThreadContextAccessor.

For the frequently needed Tenant, Principal and JWT there are also dedicated accessors:

Storing Information

The ThreadContext allows for some manipulation by the application. However, oftentimes it is more convenient to leverage the executeWith...() functionality offered by the dedicated accessors.

Consider a scenario where some part of the code should run on behalf of a specific tenant. In that case you can override the current tenant explicitly:

TenantAccessor.executeWithTenant(customTenant, () -> doStuff());
caution

Be aware, that the executeWith methods shown above only replaces the given property, but does not update properties derived from it.

Example: You have a special AuthToken, that you propagate with AuthTokenAccessor.executeWithAuthToken. Inside the given code block only the AuthToken will be replaced, while e.g. the Tenant is the same as in the original context.

If you want to start a fresh context based on a given AuthToken, for example accessing information of the provider tenant while in a subscriber context, have a look at this code:


Tenant retrieveProviderTenant()
{
// retrieves an access token from the provider context
AuthToken providerXsuaaAccessToken = AuthTokenAccessor.getXsuaaServiceToken();
return new ThreadContextExecutor()
// don't reuse the original context
.withoutParentThreadContext()
// add the provider token into the new context
.withListeners(new AuthTokenThreadContextListener(providerXsuaaAccessToken))
// return the actual provider tenant
.execute(TenantAccessor::getCurrentTenant);
}

Running Asynchronous Operations

As the name suggests the ThreadContext is bound to a thread, more specifically to the one it was created. If asynchronous operations need to access the information, it has to be propagated to the new threads.

The following code achieves this:

ThreadContextExecutor executor = new ThreadContextExecutor();
Callable operationWithContext = () -> executor.execute(() -> operation());

invokeAsynchronously(operationWithContext);

Take note that the ThreadContextExecutor is created before performing the asynchronous operation. This is important because only at that time the context is available and will be propagated.

A similar approach can be applied with the Tenant, Principal and AuthToken accessors. This code runs an asynchronous operation with a dedicated tenant:

Callable operationWithTenant = TenantAccessor.executeWithTenant(customTenant, () -> operation());

invokeAsynchronously(operationWithContext);
Thread Lifecycle

Be cautious with long-running, asynchronous operations. A propagated thread context will only persist as long as the thread lives that it was created on. So when the parent thread dies the context will cease to exist and no longer be available in any of the threads.

Workaround
You can work around this limitation with the following approach:
RequestHeaderContainer requestHeaders = RequestHeaderAccessor.tryGetHeaderContainer().getOrElse(RequestHeaderContainer.EMPTY);
ThreadContextExecutor executor = new ThreadContextExecutor().withThreadContext(new DefaultThreadContext())
.withListeners(new RequestHeaderThreadContextListener(requestHeaders));

Callable operationWithContext = () -> executor.execute(() -> operation());
invokeAsynchronously(operationWithContext);
Rate this page