Search

    Request Contexts

    Request Contexts span the execution of multiple events on (different) services. They provide a common context to these events, by providing user or tenant information or access to headers or query parameter.

    Content

    Overview

    When events are processed on services Event Context objects are used to store information related to a specific event. However when processing an HTTP request in a protocol adapter or receiving an asynchronous event from a messaging system not only a single event is triggered. Other services, like the Persistence Service or additional technical services might be involved in processing. All of these services and their event handler need access to certain overarching metadata, such as user information, the selected locale, the tenant or headers and query parameters.

    The CAP Java SDK manages and exposes this kind of information by means of RequestContext instances. They define a scope that is typically determined by the context of a single HTTP request. The active Request Context can be accessed from the Event Context. However those two are managed independently, as Event Contexts are passed along event handlers, while Request Contexts are maintained as thread-locals.

    Inside an event handler it is guaranteed that a Request Context is available. How to access the exposed information is described in detail in Reading Request Contexts.

    Usually, the protocol adapter opens a single Request Context that makes the request’s parameters available to CAP services used during request processing. In contrast an OData $batch request sequentially opens different Request Contexts with divergent parameters for the different requests inside the batch. In general, it’s possible to explicitly define (nested) Request Contexts and control their scope of validity. All events triggered within the same context also share the same parameters. This is described in detail in Defining Request Contexts.

    How to propagate Request Context instances to several threads is explained in Passing Request Contexts to Threads, and Registering Global Parameter Providers shows how you can influence or even override the standard way of retrieving the parameters from the request.

    Reading Request Contexts

    The Request Context provides information about the request’s parameters as well as the current user:

    • UserInfo: Exposes an API to fetch data of the (authenticated) user such as the logon name, id, CAP roles, and the tenant.
    • ParameterInfo: Exposes an API to retrieve additional request data such as header values, query parameters, and the locale.

    You can get an instance of UserInfo and ParameterInfo from the Event Context:

    @Before(event = CqnService.EVENT_READ)
    public void beforeRead(CdsReadEventContext context) {
        UserInfo userInfo = context.getUserInfo();
        boolean isAuthenticated = userInfo.isAuthenticated();
    
        ParameterInfo parameterInfo = context.getParameterInfo();
        Instant validFrom = parameterInfo.getValidFrom();
        // ...
    }
    

    When running in Spring, you can also use Dependency Injection:

    @Autowired
    UserInfo userInfo;
    
    @Autowired
    ParameterInfo parameterInfo;
    
    @Before(event = CqnService.EVENT_READ)
    public void beforeRead() {
    	boolean isAuthenticated = userInfo.isAuthenticated();
    	Instant validFrom = parameterInfo.getValidFrom();
        // ...
    }
    

    UserInfo reflects the minimal API, which is required by generic CAP handlers (for example, for authorization). Depending on the configured authentication strategy, a lot more useful information might be presented in the user claim. For instance, XSUAA users additionally bear email, given, and family name etc. You can retrieve these properties with userInfo.getAdditionalAttribute("<property-name>"). To establish type-safe access, additional attributes may also be accessed via custom extensions of UserInfo. To map XSUAA users, interface XsuaaUserInfo is available by default. You can create XsuaaUserInfo instances either by calling userInfo.as(XsuaaUserInfo.class) or by Spring injection:

    @Autowired
    XsuaaUserInfo xsuaaUserInfo;
    
    @Before(event = CqnService.EVENT_READ)
    public void beforeRead() {
    	boolean isAuthenticated = xsuaaUserInfo.isAuthenticated();
    	String email = xsuaaUserInfo.getEmail();
    	String givenName = xsuaaUserInfo.getGivenName();
    	String familyName = xsuaaUserInfo.getFamilyName();
        // ...
    }
    

    The same functionality is provided for arbitrary custom interfaces, which are extensions of UserInfo.

    Defining Request Contexts

    The CAP Java SDK allows you to create new Request Contexts and define their scope. This helps you to control, which set of parameters is used when events are processed by services. To manually add, modify or reset specific attributes within the scope of a new Request Context, you can use the RequestContextRunner API:

    List<Books> readBooksNotLocalized(EventContext context) {
    	return context.getCdsRuntime().requestContext().modifyParameters(param -> param.setLocale(null)).run(newContext -> {
    		return persistenceService.run(Select.from(Books_.class)).listOf(Books.class);
    	});
    }
    

    In the example, executing the CQN Select query on the Persistence Service needs to run inside a Request Context without locale in order to retrieve unlocalized data. To achieve this, a new Request Context is explicitly created by calling requestContext() on the current CdsRuntime. The code being executed in the passed java.util.function.Function is triggered with the run() method. Before the execution, the newly created context that wraps the functional code, can be modified arbitrarily:

    • modifyParameters(): Allows to add, modify, or remove (single) parameters.
    • clearParameters(): Resets all parameters.
    • providedParameters(): Resets the parameters according to the registered ParameterInfoProvider (see details below).

    Similarly, it’s possible to fully control the UserInfo instance provided in the RequestContext. It’s guaranteed, that the original parameters aren’t touched by the nested RequestContext. In addition, all original parameter values, which aren’t removed or modified are visible in the nested scope. This enables you to either define the parameters from scratch or just to put a modification layer on top.

    Some more examples:

    • modifyUser(user -> user.removeRole("read").setTenant(null).run(...): Creates a context with a user similar to the outer context but without role read and tenant.
    • anonymousUser().run(...): Runs with anonymous user.
    • modifyParameters(param -> param.setHeader("MY-HEADER", "my value")): Adds a header parameter MY-HEADER:my value.

    The modifications can be combined arbitrarily in fluent syntax.

    Registering Global Parameter Providers

    The CAP Java SDK ensures that each Request Context provides a valid instance of UserInfo as well as of ParameterInfo. Hence, if a service is called outside the scope of a context, the runtime has to create a temporary context wrapping this service call. To accomplish this automatic Request Context creation, the runtime can use the provider APIs, UserInfoProvider resp. ParameterInfoProvider. The default providers registered to the CsdRuntime derive the required information from the HTTP request, if available. Hence, HTTP/REST-based adapters can send service events without spawning a Request Context explicitly.

    The runtime’s provider interface allows customization, that means, the way how UserInfo or ParameterInfo are resolved can be modified or replaced. For example, in Deploy with Confidence (DwC) scenarios with mutual TLS (and thus without XSUAA integration), the user information can’t be derived from a principal attached to the current thread as done in the default UserInfoProvider. Authentication is done outside the service and user information is passed via dedicated header parameters. A custom provider to support this could look like in this sketch:

    @Component
    @Order(1)
    public class DwcUserInfoProvider implements UserInfoProvider {
        @Autowired
        DwcHeaderFacade dwcHeaderFacade; // accesses DwC information type-safely
    
        @Override
        public UserInfo get() {
        	return UserInfo.create()
            	.setTenant(dwcHeaderFacade.getTenant())
            	.setName(dwcHeaderFacade.getUserName())
            	.setRoles(dwcHeaderFacade.getScopes());
        }
    }
    

    It’s allowed to define several providers of the same type. In Spring, the provider with the lowest @Order will be called first (in plain Java the order is given by registration order). You can reuse the provider with lower priority and build a modified result. To accomplish this, remember the previous provider instance, which is passed during registration via setPrevious() method call. Such a chain of providers can be used to normalize user names or adjust user roles to match specific needs.

    Passing Request Contexts to Threads

    CAP service calls can be executed in different threads. In most cases the Request Context from the parent thread - typically the worker thread executing the request - needs to be propagated to one or more child threads. Otherwise, required parameter and user information might be missing, for example, when authorizing CRUD events or creating tenant-specific database connections.

    To propagate the parent context, create an instance of RequestContextRunner in the parent thread and open a new RequestContext with run() method in the child thread. This way all parameters from the parent context are also available in the context of one or more spawned threads, as demonstrated in the following example:

    RequestContextRunner runner = runtime.requestContext();
    Future<Result> result = Executors.newSingleThreadExecutor().submit(() -> {
    	return runner.run(threadContext -> {
    		return persistenceService.run(Select.from(Books_.class));
    	});
    });
    

    You’re free to modify the parameters by means of the API described in Defining Request Contexts in addition. But please be ware that providedParameters() resp. providedUser() might lead to unexpected behavior as typically the standard providers require to run in the context of the original worker thread to access request-local data.

    Show/Hide Beta Features