Skip to content

Commit f7a04e6

Browse files
beckermarcgithub-actions[bot]renejeglinsky
authored
Java: Docs for improved event handler signatures (#1944)
- Arbitrary return types - Service arguments - Entity reference arguments --------- Co-authored-by: github-actions[bot] <41898282+github-actions[bot]@users.noreply.github.com> Co-authored-by: Rene Jeglinsky <rene.jeglinsky@sap.com>
1 parent b2770f2 commit f7a04e6

1 file changed

Lines changed: 96 additions & 31 deletions

File tree

java/event-handlers/index.md

Lines changed: 96 additions & 31 deletions
Original file line numberDiff line numberDiff line change
@@ -14,28 +14,28 @@ uacp: Used as link target from SAP Help Portal at https://help.sap.com/products/
1414
</style>
1515

1616
This section describes how to register event handlers on services. In CAP everything that happens at runtime is an [event](../../about/best-practices#events) that is sent to a [service](../../about/best-practices#services).
17-
With event handlers the processing of these events can be extended or overridden. Event handlers can be used to handle CRUD events, implement actions and functions and to handle asynchronous events from a messaging service.
17+
With event handlers, the processing of these events can be extended or overridden. Event handlers can be used to handle CRUD events, implement actions and functions, and to handle asynchronous events from a messaging service.
1818

1919
## Introduction to Event Handlers
2020

2121
CAP allows you to register event handlers for [events](../../about/best-practices#events) on [services](../../about/best-practices#services). An event handler is simply a Java method.
2222
Event handlers enable you to add custom business logic to your application by either extending the processing of an event, or by completely overriding its default implementation.
2323

2424
::: tip
25-
Event handlers are a powerful means to extend CAP. Did you know, that most of the built-in features provided by CAP are implemented using event handlers?
25+
Event handlers are a powerful means to extend CAP. Did you know that most of the built-in features provided by CAP are implemented using event handlers?
2626
:::
2727

2828
Common events are the CRUD events (`CREATE`, `READ`, `UPDATE`, `DELETE`), which are handled by the different kinds of [CQN-based services](../cqn-services/#cdsservices).
29-
These events are most typically triggered, when an HTTP-based protocol adapter (for example OData V4) executes a CQN statement on an Application Service to fulfill the HTTP request.
29+
These events are most typically triggered when an HTTP-based protocol adapter (for example OData V4) executes a CQN statement on an Application Service to fulfill the HTTP request.
3030
The CAP Java SDK provides a lot of built-in event handlers (also known as [Generic Providers](../../guides/providing-services)) that handle CRUD operations out of the box and implement the handling of many CDS annotations.
3131
Applications most commonly use event handlers on CRUD events to _extend_ the event processing by using the [`Before`](#before) and [`After`](#after) phase.
3232

3333
[Actions](../../cds/cdl#actions) and [Functions](../../cds/cdl#actions) that are defined by an Application Service in its model definition are mapped to events as well.
3434
Therefore, to implement the business logic of an action or function, you need to register event handlers as well.
3535
Event handlers that implement the core processing of an event should be registered using the [`On`](#on) phase.
3636

37-
Events in CAP can have parameters and - in case they are synchronous - a return value. The CAP Java SDK uses [Event Contexts](#eventcontext) to provide a type-safe way to access parameters and return values.
38-
In the case of CRUD events the corresponding Event Contexts provide for example access to the CQN statement. Event Contexts can be easily obtained in an event handler.
37+
Events in CAP can have parameters and - in case they're synchronous - a return value. The CAP Java SDK uses [Event Contexts](#eventcontext) to provide a type-safe way to access parameters and return values.
38+
In the case of CRUD events the corresponding Event Contexts provide, for example, access to the CQN statement. Event Contexts can be easily obtained in an event handler.
3939

4040
## Event Phases { #phases}
4141

@@ -71,7 +71,7 @@ The `On` phase is completed when one of the following conditions applies:
7171
- A handler throws an exception. In this case, event processing is terminated immediately.
7272

7373
In case of synchronous events, if after the `On` phase, no handler completed the event processing, it's considered an error and the event processing is aborted with an exception.
74-
However when registering an `On` handler for an asynchronous event it is not recommended to complete the event processing, as other handlers might not get notified of the event anymore.
74+
However, when registering an `On` handler for an asynchronous event it is not recommended to complete the event processing, as other handlers might not get notified of the event anymore.
7575
In that case CAP ensures to auto-complete the event, once all `On` handlers have been executed.
7676

7777
### After { #after}
@@ -108,12 +108,12 @@ srv.emit(context); // process event
108108
Object result = context.get("result");
109109
```
110110

111-
Using the `get` and `put` methods has several drawbacks: The API is neither type-safe nor is it clear what the correct keys for different event parameters are.
112-
To solve these issues it is possible to overlay the general Event Context with an event-specific Event Context, which provides typed getters and setters for the parameters of a specific event.
113-
For each event that the CAP Java SDK provides out-of-the-box (for example the [CRUD events](../cqn-services/application-services#crudevents)) a corresponding Event Context is provided.
111+
Using the `get` and `put` methods has several drawbacks: The API is not type-safe and it's not clear what the correct keys for different event parameters are.
112+
To solve these issues, it is possible to overlay the general Event Context with an event-specific Event Context, which provides typed getters and setters for the parameters of a specific event.
113+
For each event that the CAP Java SDK provides out of the box (for example the [CRUD events](../cqn-services/application-services#crudevents)) a corresponding Event Context is provided.
114114

115115
Let's have a look at an example. The [CdsReadEventContext](https://www.javadoc.io/doc/com.sap.cds/cds-services-api/latest/com/sap/cds/services/cds/CdsReadEventContext.html) interface is the `READ` event-specific Event Context.
116-
As one of the parameters of the `READ` event is a [CqnSelect](../../cds/cqn#select) it provides a `CqnSelect getCqn()` method. The return value of a `READ` event is a [Result](../working-with-cql/query-execution#result).
116+
As one of the parameters of the `READ` event is a [CqnSelect](../../cds/cqn#select), it provides a `CqnSelect getCqn()` method. The return value of a `READ` event is a [Result](../working-with-cql/query-execution#result).
117117
The context therefore also provides a `Result getResult()` and a `setResult(Result r)` method. You can use the `as` method provided by the general Event Context to overlay it:
118118

119119
```java
@@ -124,7 +124,7 @@ Result result = context.getResult();
124124
```
125125

126126
The getter and setter methods, still operate on the simple get/put API shown in the previous example. They just provide a type-safe layer on top of it.
127-
The `as` method makes use of Java Proxies behind the scenes. Therefore, an interface definition is all that is required to enable this functionality.
127+
The `as` method uses Java Proxies behind the scenes. Therefore, an interface definition is all that is required to enable this functionality.
128128

129129
::: tip
130130
Use these event-specific type-safe Event Context interfaces whenever possible.
@@ -219,12 +219,12 @@ public class AdminServiceHandler implements EventHandler {
219219
:::
220220

221221
- The annotation `@Component` instructs Spring Boot to create a bean instance from this class.
222-
- The `EventHandler` marker interface is required for CAP to identify the class as an event hander class among all beans and scan it for event handler methods.
223-
- The optional `@ServiceName` annotation can be used to specify the default service, which event handlers are registered on. It is possible to override this value for specific event handler methods.
222+
- The `EventHandler` marker interface is required for CAP to identify the class as an event handler class among all beans and scan it for event handler methods.
223+
- The optional `@ServiceName` annotation can be used to specify the default service, which event handlers are registered on. It's possible to override this value for specific event handler methods.
224224

225225
::: tip
226226
The CAP Java SDK Maven Plugin generates interfaces for services in the CDS model. These interfaces provide String constants with the fully qualified name of the service.
227-
In case the service name is based on the CDS model it is recommended to use these constants with the `@ServiceName` annotation.
227+
If the service name is based on the CDS model, it's recommended to use these constants with the `@ServiceName` annotation.
228228
:::
229229

230230
It is possible to specify multiple service names. Event handlers are registered on all of these services.
@@ -252,10 +252,10 @@ Each of these annotations can define the following attributes:
252252
- `serviceType`: The type of services the event handler is registered on, for example, `ApplicationService.class`. Can be used together with `service = "*"` to register an event handler on all services of a certain type.
253253

254254
- `event`: The events the event handler is registered on. The event handler is invoked in case any of the events specified matches the current event. Use `*` to match any event.
255-
It's optional, if the event can be inferred through a [Event Context argument](#contextarguments) in the handler signature.
255+
It's optional, if the event can be inferred through an [Event Context argument](#contextarguments) in the handler signature.
256256

257-
- `entity`: The target entities the event handler is registered on. The event handler is invoked in case any of the entities specified matches the current entity. Use `*` to match any entity.
258-
It's optional, if the entity can be inferred through a [POJO-based argument](#pojoarguments) in the handler signature. If no value is specified or can be inferred it defaults to `*`.
257+
- `entity`: The target entities that the event handler is registered on. The event handler is invoked in case any of the entities specified matches the current entity. Use `*` to match any entity.
258+
It's optional, if the entity can be inferred through a [POJO-based argument](#pojoarguments) in the handler signature. If no value is specified or can be inferred, it defaults to `*`.
259259

260260
::: tip
261261
The interfaces of different service types provide String constants for the events they support (see for example the [CqnService](https://www.javadoc.io/doc/com.sap.cds/cds-services-api/latest/com/sap/cds/services/cds/CqnService.html)).
@@ -278,7 +278,7 @@ It is recommended to use these constants with the `event` or `entity` attributes
278278

279279
## Event Handler Method Signatures { #handlersignature}
280280

281-
The most basic signature of an event handler method is `public void process(EventContext context)`. However event-specific Event Context and entity data arguments and certain return values are supported as well and can be freely combined.
281+
The most basic signature of an event handler method is `public void process(EventContext context)`. However, event-specific Event Context and entity data arguments and return values are supported as well and can be freely combined.
282282
It is even valid for event handler methods to have no arguments at all. Handler methods don't necessarily have to be public methods. They can also be methods with protected, private, or package visibility.
283283

284284
### Event Context Arguments { #contextarguments}
@@ -291,7 +291,7 @@ An event handler can get access to the general `EventContext` by simply declarin
291291
public void readBooks(EventContext context) { }
292292
```
293293

294-
It is also possible to directly refer to event-specific Event Context interfaces in your arguments. In that case the general Event Context is automatically overlayed with the event-specific one:
294+
It is also possible to directly refer to event-specific Event Context interfaces in your arguments. In that case the general Event Context is automatically overlaid with the event-specific one:
295295

296296
```java
297297
@Before(event = CqnService.EVENT_READ, entity = Books_.CDS_NAME)
@@ -311,7 +311,7 @@ The mapping between an Event Context interface and an event, is based on the `@E
311311
:::
312312

313313
In case an event handler is registered on multiple events only the general Event Context argument can be used.
314-
At runtime, the corresponding event-specific Event Context can be overlayed explicitly, if access to event-specific parameters is required:
314+
At runtime, the corresponding event-specific Event Context can be overlaid explicitly, if access to event-specific parameters is required:
315315

316316
```java
317317
@Before(event = { CqnService.EVENT_CREATE, CqnService.EVENT_UPDATE }, entity = Books_.CDS_NAME)
@@ -326,11 +326,9 @@ public void changeBooks(EventContext context) {
326326
}
327327
```
328328

329-
330-
331329
### Entity Data Arguments { #pojoarguments}
332330

333-
When adding business logic to an Application Service event handlers most commonly need to access entity data.
331+
When adding business logic to an Application Service, event handlers most commonly need to access entity data.
334332
Entity data can be directly accessed in the event handler method, by using an argument of type `CdsData`:
335333

336334
```java
@@ -368,17 +366,17 @@ Entity data arguments only work on [CRUD events](../cqn-services/application-ser
368366

369367
The origin from which the entity data is provided depends on the phase of the event processing.
370368
During the `Before` and `On` phase it is obtained from the CQN statement. The CQN statement contains the entity data that was provided by the service client.
371-
However during the `After` phase the entity data is obtained from the `Result` object, which is provided as the return value of the event to the service client.
369+
However, during the `After` phase the entity data is obtained from the `Result` object, which is provided as the return value of the event to the service client.
372370
Some CQN statements such as for example `CqnSelect`, which is used with `READ` events, don't allow to carry data. In these cases entity data arguments are set to `null`.
373371

374-
There are different flavours of entity data arguments. Besides using `List<Books>` it is also possible to use `Stream<Books>`:
372+
There are different flavors of entity data arguments. Besides using `List<Books>`, it's also possible to use `Stream<Books>`:
375373

376374
```java
377375
@Before(event = { CqnService.EVENT_CREATE, CqnService.EVENT_UPDATE })
378376
public void changeBooks(Stream<Books> books) { }
379377
```
380378

381-
It is also possible to use non-collection-based entity arguments, such as `Books`. However if multiple data rows are available at runtime an exception will be thrown in that case:
379+
It is also possible to use non-collection-based entity arguments, such as `Books`. However, if multiple data rows are available at runtime an exception will be thrown in that case:
382380

383381
```java
384382
@Before(event = { CqnService.EVENT_CREATE, CqnService.EVENT_UPDATE })
@@ -391,6 +389,58 @@ During the `Before` and `On` phase changes affect the data carried by the CQN st
391389
During the `After` phase changes affect the return value of the event.
392390
:::
393391

392+
### Entity Reference Arguments
393+
394+
You can get an entity reference reflecting the reference of the currently processed CQN statement, by declaring a corresponding argument in your method signature.
395+
396+
```java
397+
@After(event = CqnService.EVENT_UPDATE, entity = Books_.CDS_NAME)
398+
public void changedBook(CqnStructuredTypeRef ref) { }
399+
```
400+
401+
The CAP Java SDK Maven Plugin can generate query builder interfaces for entities defined in the CDS model. These interfaces allow you to build [type-safe queries](../working-with-cql/query-api#concepts) and can be used in arguments as well:
402+
403+
```java
404+
@After(event = CqnService.EVENT_UPDATE, entity = Books_.CDS_NAME)
405+
public void changedBook(Books_ ref) { }
406+
```
407+
408+
If an entity reference argument is used, CAP can infer the entity for the event handler registration from the entity reference argument:
409+
410+
```java
411+
@After(event = CqnService.EVENT_UPDATE)
412+
public void changedBook(Books_ ref) { }
413+
```
414+
415+
::: tip Mapping uses `@CdsName` annotation
416+
The mapping between a query builder interface and an entity is based on the `@CdsName` annotation of the query builder interface.
417+
:::
418+
419+
Entity data arguments work on all events that operate with a `CqnStatement`. This is the case for all CRUD events and custom bound actions or functions.
420+
421+
You can directly use these references to build further queries in your event handlers:
422+
423+
```java
424+
@After(event = CqnService.EVENT_UPDATE)
425+
public void changedBook(Books_ ref) {
426+
var select = Select.from(ref).columns(b -> b.title());
427+
}
428+
```
429+
430+
### Service Arguments { #servicearguments }
431+
432+
The CAP Java SDK Maven Plugin can [generate service interfaces](../cqn-services/application-services#trigger-action-or-function) for services defined in the CDS model.
433+
434+
To easily access these generated application-specific interfaces, you can declare corresponding arguments in your method signature.
435+
The same approach works for generic interfaces like `CqnService` or `DraftService`.
436+
437+
The service instances that can be provided to the event handler are always the service instances that are processing the event.
438+
439+
```java
440+
@After(event = CqnService.EVENT_UPDATE)
441+
public void changedBook(Books book, AdminService service) { }
442+
```
443+
394444
### Return Values
395445

396446
The return value of an event can be set by returning a value in an event handler method:
@@ -402,10 +452,10 @@ public Result readBooks(CdsReadEventContext context) {
402452
}
403453
```
404454

405-
In case an event handler method of the `Before` or `On` phase has a return value it automatically [completes the event processing](#eventcompletion), once it is executed.
455+
In case an event handler method of the `Before` or `On` phase has a return value it automatically [completes the event processing](#eventcompletion), once it's executed.
406456
Event handler methods of the `After` phase that have a return value, replace the return value of the event.
407457

408-
Only return values that extend `Iterable<? extends Map<String, Object>>` are supported. The `Result` object or a list of entity data (for example `List<Books>`) fulfill this requirement.
458+
For [CRUD events](../cqn-services/application-services#crudevents) and [draft-specific CRUD events](../fiori-drafts#draftevents), return values that extend `Iterable<? extends Map<String, Object>>` are supported. The `Result` object or a list of entity data (for example `List<Books>`) fulfill this requirement.
409459

410460
```java
411461
@On(entity = Books_.CDS_NAME)
@@ -416,12 +466,27 @@ public List<Books> readBooks(CdsReadEventContext context) {
416466
}
417467
```
418468

419-
Event handler methods with return values only work on [CRUD events](../cqn-services/application-services#crudevents) of [CQN-based services](../cqn-services/#cdsservices) or the [draft-specific CRUD events](../fiori-drafts#draftevents) provided by Draft Services.
420-
421469
::: tip
422-
To learn how to build your own Result objects, have a look at the [Result Builder API](../cqn-services/application-services#result-builder)
470+
To learn how to build your own Result objects, have a look at [Result Handling](../cqn-services/application-services#result-handling)
423471
:::
424472

473+
For custom actions or functions, you can directly return the return value of the corresponding action or function in your event handler:
474+
475+
Given the following CDS model:
476+
```cds
477+
service World {
478+
function hello() returns String;
479+
}
480+
```
481+
482+
The event handler can directly return a `String`, which corresponds to the return type of the `hello` function:
483+
```java
484+
@On(event = "hello")
485+
public String hello() {
486+
return "Hello World";
487+
}
488+
```
489+
425490
### Ordering of Event Handler Methods
426491

427492
You can influence the order in which the event handlers are executed by means of CAP annotation [@HandlerOrder](https://www.javadoc.io/doc/com.sap.cds/cds-services-api/latest/com/sap/cds/services/handler/annotations/HandlerOrder.html). It defines the order of handler methods within each phase of events. You may use constants `HandlerOrder.EARLY` or `HandlerOrder.LATE` to place one handler earlier or later relative to the handlers without the annotation. Note that handlers with the same `@HandlerOrder` are executed in a deterministic, but arbitrary sequence.

0 commit comments

Comments
 (0)