Technology Blogs by SAP
Learn how to extend and personalize SAP applications. Follow the SAP technology blog for insights into SAP BTP, ABAP, SAP Analytics Cloud, SAP HANA, and more.
cancel
Showing results for 
Search instead for 
Did you mean: 
charlesdubois
Employee
Employee
With the release of version 4.0.0, the ThreadContext has seen major changes in the SAP Cloud SDK.
This blog post will explain the changes we made that simplify running tasks on different threads with the ThreadContext attached.

What is a ThreadContext?


The SAP Cloud SDK for Java provides a so-called ThreadContext.
It serves as thread-safe storage for potentially sensitive information.
A few of the most important objects stored are:

  • 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.
Every servlet that is received in its own thread, will receive a ThreadContext.
This ensures different tenants and users are properly isolated.

A Typical Use Case


Let's say we need to run a lengthy operation upon receiving a request.
The simple solution would be to block the servlet for as long as the operation takes:
@RestController
public class HelloWorldController
{
@GetMapping( "/hello" )
public String getHello()
{
final ThreadContext currentContext = ThreadContextAccessor.getCurrentContext();
database.store(currentTenant);// long-running operation
return "Hello";
}
}

Because the Spring servlet has its own thread we automatically get access to the ThreadContext.

But our users now experience long loading times because the servlet takes a long time to answer.
We need to offload the long-running operation to another thread.

Asynchronous Operations with SAP Cloud SDK Version 3


We can call an @Async annotated method:
@RestController
public class HelloWorldController
{
@Autowired
private DatabaseAccess databaseAccess;

@GetMapping( "/hello" )
public String getHello()
{
databaseAccess.store();
return "Hello";
}
}

The @Async method must be public in another class to be executed in a separate thread:
@EnableAsync
@Configuration
public class DatabaseAccess
{
@Async
public String store()
{
final ThreadContext currentContext = ThreadContextAccessor.getCurrentContext();// doesn't work
database.store(currentTenant);// long-running operation
return "success";
}
}

However, this breaks the Multi Tenancy of the SAP Cloud SDK.
We cannot access the current tenant in the new thread.

Propagate the ThreadContext


We can try to use the ThreadContextExecutor to propagate the ThreadContext to the new thread:
@RestController
public class HelloWorldController
{
@Autowired
private DatabaseAccess databaseAccess;

@GetMapping( "/hello" )
public String getHello()
{
final ThreadContextExecutor executor = new ThreadContextExecutor();
databaseAccess.store(executor);
return "Hello";
}
}

@EnableAsync
@Configuration
public class DatabaseAccess
{
@Async
public String store( ThreadContextExecutor executor )
{
return executor.execute(() -> {
final ThreadContext currentContext = ThreadContextAccessor.getCurrentContext();// unreliable
database.store(currentTenant);// long-running operation
return "success";
});
}
}

But the access is unreliable because the ThreadContext will get removed when the servlet is finished.
We can also see that the resulting code has a lot of boilerplate code.
Let's take a look at how this use case would be done with SAP Cloud SDK 4.

Asynchronous Operations with SAP Cloud SDK Version 4


With version 4 you can simplify your code for running asynchronous tasks with the newly introduced ThreadContextExecutors:
Future runningTask = ThreadContextExecutors.submit(() -> operation());

This functionality is conveniently integrated to work with Springs @Async annotation like so:
@RestController
public class HelloWorldController
{
@Autowired
private DatabaseAccess databaseAccess;

@GetMapping( "/hello" )
public String getHello()
{
databaseAccess.store();
return "Hello";
}
}

@Configuration
public class DatabaseAccess
{
@Async
public void store()
{
database.store(TenantAccessor.getCurrentTenant());// long-running operation
}
}

This is thanks to this @Configuration all methods annotated with @Async will have the ThreadContext available:
@EnableAsync
@Configuration
public class AsynchronousConfiguration implements AsyncConfigurer
{
@Override
public Executor getAsyncExecutor()
{
return ThreadContextExecutors.getExecutor();
}
}

This already comes out of the box on newly generated applications such as scp-cf-spring.
You can read more about the @Async functionality here.

Configuring the Executor


The ThreadContextExecutors class leverages a single ThreadContextExecutorService instance that can be configured.

You can create a custom ThreadContextExecutorService, for example to use a different thread pool, via:
ThreadContextExecutorService executor = DefaultThreadContextExecutorService
.of(Executors.newFixedThreadPool(3));

// use it directly:
executor.submit(myTask);

// or set it to be used by the static ThreadContextExecutors API:
ThreadContextExecutors.setExecutor(executor);
ThreadContextExecutors.submit(myTask);

CAP Integration


When using CAP the tenant, principal, and headers are derived from the RequestContext.
Use the cds-integration-cloud-sdk dependency to propagate the context out of the box when using our APIs.
Please refer to the documentation on how to override existing values in the CAP context.

Summary


The new ThreadContextExecutors allows you to run tasks on different threads with the ThreadContext attached.
It can be integrated with Spring and CAP, and can also be configured.
For more details, please refer to the documentation or the upgrade guide.

Share Your Feedback


Do you have any questions on the new features?
Or are you struggling with the upgrade?
Don’t hesitate to share your feedback in the comments below, ask a question in the Q&A or to create a dedicated issue on our GitHub.