This is a supplementary article to the recent blog by Daniel Schlachter. As CAP has 2 runtime stacks, namely CAP Java and CAP node.js, you might wonder how the described plugin mechanism looks like in CAP Java. To be honest, I have to correct Daniel a bit. While plugin concept is new to CAP node.js it’s has been part of CAP Java’s architecture ever since. We had just not advertised or documented these capabilities so far. 😉
The implementation of the application features
of CAP Java itself is based on the same extension and plugin-mechanisms as one would use to build an external plugin:
So, let’s shed some light on this and see how easy it actually is to build reusable plugin component for CAP Java applications. In order to have an comparable sample to the one Daniel has built in his article I chose the same scenario.
The requirement is that book titles will be appended with an emoji when read from our service. In order to have this available for more than one service this is built as a reuse component instead of a custom handler inside your application.
Prerequisites
- Java 8 or later
- Maven 3.x
Build the basic application
At first you can generate a new CAP Java application with parts of the bookshop sample using this command:
cds init books --add java,samples
Then, go start the application with the following commands:
cd books
mvn spring-boot:run
You can now load all books running a GET request on http://localhost:8080/odata/v4/CatalogService/Books which then will return something like this:
{
"@context":"$metadata#Books",
"@metadataEtag":"W/"7966b04ab5aa9c3bf779789e1d6bbd08b9a7645ba87661c80464af5e80d8de04"",
"value":[
{"ID":1,"title":"Wuthering Heights","stock":100},
{"ID":2,"title":"Jane Eyre (discounted)","stock":500}
]
}
Build the plugin
A plugin to a CAP Java application needs to be developed in a seperate Maven artifact and therefore in a separate repository. It will be later integrated into your application as a <dependency>
in the Maven build descriptor. So, create a new directory named cds-emoji-plugin-java
and cd
to that directory.
At first, we need a build descriptor. Take the following XML source and paste it to a file named pom.xml
in the new directory:
pom.xml
<?xml version="1.0" encoding="UTF-8"?>
<project xmlns="http://maven.apache.org/POM/4.0.0"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 https://maven.apache.org/xsd/maven-4.0.0.xsd">
<modelVersion>4.0.0</modelVersion>
<groupId>com.sap.example.cds.plugins</groupId>
<artifactId>cds-emoji-plugin-java</artifactId>
<packaging>jar</packaging>
<version>0.1.0-SNAPSHOT</version>
<properties>
<maven.compiler.source>8</maven.compiler.source>
<maven.compiler.target>8</maven.compiler.target>
<cds.services.version>1.34.0</cds.services.version>
</properties>
<dependencyManagement>
<dependencies>
<dependency>
<groupId>com.sap.cds</groupId>
<artifactId>cds-services-bom</artifactId>
<version>${cds.services.version}</version>
<type>pom</type>
<scope>import</scope>
</dependency>
</dependencies>
</dependencyManagement>
<dependencies>
<dependency>
<groupId>com.sap.cds</groupId>
<artifactId>cds-services-api</artifactId>
</dependency>
</dependencies>
</project>
Then, we need to create a directory structure in that we can place the actual code of the extension. All command line commands will be for MacOS and Linux. Windows users might need to transfer this to Windows commands. 😉
mkdir -p src/main/java/com/sap/example/cds
mkdir -p src/main/resources/META-INF/services
You can now use your favourite Java IDE or just continue at the command line. We need two Java classes in the src/main/java/com/sap/example/cds
directory. The first is the actual implementation of the plugin. Basically it looks pretty much the same as a custom handler in your CAP Java application would look like. To append the emoji, we use CAP Java’s powerful data processor:
src/main/java/com/sap/example/cds/EmojiHandler.java
:
package com.sap.example.cds;
import com.sap.cds.CdsDataProcessor;
import com.sap.cds.services.cds.ApplicationService;
import com.sap.cds.services.cds.CdsReadEventContext;
import com.sap.cds.services.handler.EventHandler;
import com.sap.cds.services.handler.annotations.After;
import com.sap.cds.services.handler.annotations.ServiceName;
@ServiceName(value = "*", type = ApplicationService.class)
public class EmojiHandler implements EventHandler {
private static final String EMOJI_ANNOTATION_NAME = "@emoji";
@After
public void decorateEmoji(CdsReadEventContext context) {
CdsDataProcessor.create()
.addConverter(
(path, element, type) -> element.findAnnotation(EMOJI_ANNOTATION_NAME).isPresent(),
(path, element, value) -> value + "")
.process(context.getResult());
}
}
The class contains an @After
handler that will listen for READ events for all entities on all ApplicationServices. In case a read entity contains an element annotated with @emoji
it will be appended with the 🙃 emoji. That’s all.
Along the actual plugin you need an implementation of the CdsRuntimeConfiguration
interface. This implementation will be called by the CAP Java runtime and contains the logic needed to create the EmojiHandler class above. Luckily, it’s pretty easy to be created:
src/main/java/com/sap/example/cds/EmojiHandlerRuntimeConfiguration.java:
package com.sap.example.cds;
import com.sap.cds.services.runtime.CdsRuntimeConfiguration;
import com.sap.cds.services.runtime.CdsRuntimeConfigurer;
public class EmojiHandlerRuntimeConfiguration implements CdsRuntimeConfiguration {
@Override
public void eventHandlers(CdsRuntimeConfigurer configurer) {
configurer.eventHandler(new EmojiHandler());
}
}
In the end the CAP Java runtime needs to be able to recover the configuration class without knowing it’s actual name. For this task CAP Java uses the Service Loader approach that is part of the JDK. On the plugin side you only need to provide a text file at a defined location having the filename of the fully qualified name of the interface being implemented.
src/main/resources/META-INF/services/com.sap.cds.services.runtime.CdsRuntimeConfiguration:
com.sap.example.cds.EmojiHandlerRuntimeConfiguration
Now you can build the plugin and install it to your local Maven repository. Afterwards it can be consumed by our CAP Java application that we created in the first step.
mvn install
Consume the extension
Now that we have developed and provided the extension we can import it in the CAP Java application that we have built at the beginning of this blog.
Open the build descriptor of the srv module (srv/pom.xml
) of the application and add the following snippet to the <dependencies>
section:
<dependency>
<groupId>com.sap.example.cds.plugins</groupId>
<artifactId>cds-emoji-plugin-java</artifactId>
<version>0.1.0-SNAPSHOT</version>
<scope>runtime</scope>
</dependency>
Afterwards, annotate the element title
of the entity Books
with @emoji
:
namespace my.bookshop;
entity Books {
key ID : Integer;
@emoji
title : String;
author : String;
stock : Integer;
}
Now you can restart the application and repeat the OData request from the beginning of this blog. The title of the book will now be appended with the 🙃 emoji (without an UI it’s however displayed as the Unicode escape sequence uD83DuDE43
).
Wrap up
So, that was the whole trick. 🙂 We have built a very simple CAP Java application with a few commands on the command line and then built a minimal, generic event handler plugin that is registered using the CdsRuntimeConfiguration
and Java’s Service Loader mechanism.