Skip to content

Commit da0acb7

Browse files
committed
add plugin inline
1 parent 2f3cd36 commit da0acb7

File tree

6 files changed

+373
-0
lines changed

6 files changed

+373
-0
lines changed

criticality/.gitignore

Lines changed: 56 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,56 @@
1+
# CAP sflight
2+
_out
3+
*.db
4+
connection.properties
5+
default-*.json
6+
schema.sql
7+
gen/
8+
node_modules/
9+
target/
10+
.reloadtrigger
11+
@cds-models/
12+
13+
# Web IDE, App Studio
14+
.che/
15+
.gen/
16+
17+
# MTA
18+
*_mta_build_tmp
19+
*.mtar
20+
mta_archives/
21+
/Makefile_*.mta
22+
23+
# Other
24+
.DS_Store
25+
.npmrc
26+
*.orig
27+
*.log
28+
29+
*.flattened-pom.xml
30+
31+
srv/src/main/resources/edmx/**
32+
dist/
33+
/app/resources/
34+
/app/router/package-lock.json
35+
36+
37+
# IDEs
38+
.idea
39+
*.iml
40+
.project
41+
.settings
42+
.classpath
43+
# .vscode/*
44+
# IMPORTANT: Do not exclude .vscode please!
45+
46+
# Local dev
47+
.env
48+
.values.yaml
49+
.cdsrc-private.json
50+
51+
# Exclude Helm chart for this example
52+
/chart
53+
54+
# Experimental
55+
xxx_*
56+
xxx/*

criticality/pom.xml

Lines changed: 43 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,43 @@
1+
<project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
2+
xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/maven-v4_0_0.xsd">
3+
<modelVersion>4.0.0</modelVersion>
4+
<groupId>com.sap.capire</groupId>
5+
<artifactId>criticality</artifactId>
6+
<packaging>jar</packaging>
7+
<version>1.0-SNAPSHOT</version>
8+
<name>criticality</name>
9+
<url>http://maven.apache.org</url>
10+
<properties>
11+
<maven.compiler.release>17</maven.compiler.release>
12+
<cds.services.version>2.10.0</cds.services.version>
13+
</properties>
14+
<dependencyManagement>
15+
<dependencies>
16+
<dependency>
17+
<groupId>com.sap.cds</groupId>
18+
<artifactId>cds-services-bom</artifactId>
19+
<version>${cds.services.version}</version>
20+
<type>pom</type>
21+
<scope>import</scope>
22+
</dependency>
23+
</dependencies>
24+
</dependencyManagement>
25+
<dependencies>
26+
<dependency>
27+
<groupId>org.springframework.boot</groupId>
28+
<artifactId>spring-boot-autoconfigure</artifactId>
29+
<version>3.2.6</version>
30+
</dependency>
31+
<dependency>
32+
<groupId>com.sap.cds</groupId>
33+
<artifactId>cds-services-api</artifactId>
34+
<version>2.10.0</version>
35+
</dependency>
36+
<dependency>
37+
<groupId>junit</groupId>
38+
<artifactId>junit</artifactId>
39+
<version>3.8.1</version>
40+
<scope>test</scope>
41+
</dependency>
42+
</dependencies>
43+
</project>

criticality/readme.md

Lines changed: 155 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,155 @@
1+
# Create a crititcality plugin for CAP Java
2+
3+
## Prerequisites
4+
5+
**In case you are using SAP Business Application Studio or SAP Build Code you can skip this section.**
6+
7+
You need to have the following tools installed:
8+
9+
* Java >=17 (internally, we use sapmachine but any other vendor should work, too)
10+
* Maven (some recent 3.x release)
11+
* Your Java IDE of choice. Ideally with CDS Tooling installed. This would be VS Code or IntelliJ Idea Ultimate.
12+
13+
## Requirement
14+
15+
Write a CAP Java plugin that provides a handler that can detect CDS enum values annotated with `@criticality.*` and
16+
ets the integer value according to the [criticality OData vocabulary](https://sap.github.io/odata-vocabularies/vocabularies/UI.html#CriticalityType)
17+
to an `criticality` element of the same entity.
18+
19+
Bones points: this works for expanded entities, too.
20+
21+
## Create a new plain Java project with Maven
22+
23+
Use the Maven quickstart archetype to generate a plain, empty Java project:
24+
25+
```
26+
mvn archetype:generate -DgroupId= -DartifactId=criticality -DarchetypeGroupId=com.sap.capire -DarchetypeArtifactId=maven-archetype-quickstart -Dversion=1.0-SNAPSHOT -DinteractiveMode=false
27+
```
28+
29+
30+
## Add needed dependencies
31+
32+
Replace the generated pom.xml with the following content. It contains the needed dependencies and the correct Java version:
33+
34+
```
35+
<project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
36+
xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/maven-v4_0_0.xsd">
37+
<modelVersion>4.0.0</modelVersion>
38+
<groupId>com.sap.capire</groupId>
39+
<artifactId>criticality</artifactId>
40+
<packaging>jar</packaging>
41+
<version>1.0-SNAPSHOT</version>
42+
<name>criticality</name>
43+
<url>http://maven.apache.org</url>
44+
<properties>
45+
<maven.compiler.release>17</maven.compiler.release>
46+
<cds.services.version>2.10.0</cds.services.version>
47+
</properties>
48+
<dependencyManagement>
49+
<dependencies>
50+
<dependency>
51+
<groupId>com.sap.cds</groupId>
52+
<artifactId>cds-services-bom</artifactId>
53+
<version>${cds.services.version}</version>
54+
<type>pom</type>
55+
<scope>import</scope>
56+
</dependency>
57+
</dependencies>
58+
</dependencyManagement>
59+
<dependencies>
60+
<dependency>
61+
<groupId>org.springframework.boot</groupId>
62+
<artifactId>spring-boot-autoconfigure</artifactId>
63+
<version>3.2.6</version>
64+
</dependency>
65+
<dependency>
66+
<groupId>com.sap.cds</groupId>
67+
<artifactId>cds-services-api</artifactId>
68+
<version>2.10.0</version>
69+
</dependency>
70+
<dependency>
71+
<groupId>junit</groupId>
72+
<artifactId>junit</artifactId>
73+
<version>3.8.1</version>
74+
<scope>test</scope>
75+
</dependency>
76+
</dependencies>
77+
</project>
78+
```
79+
80+
Perform the initial build with `mvn compile`.
81+
82+
## Spring Boot Auto Configuration for the handler class
83+
84+
Use Spring Boot autoconfiguration to register the handler automatically as soon the dependency is added to the pom.xml.
85+
86+
## Implement the handler
87+
88+
Write the handler. Use the following resources as a reference:
89+
90+
* https://cap.cloud.sap/docs/java/reflection-api You use the reflection API to introspect the application's CDS model in
91+
order to find relevant entities and elements.
92+
* https://cap.cloud.sap/docs/java/cds-data In the handler, you need to traverse the data in the result and act according
93+
to the information you discovered in the CDS model.
94+
95+
## Install the handler to the local Maven repo
96+
In order to consume the new plugin from e.g. the Incidents App you need to install it to the local Maven repo. The `source:jar`
97+
goal adds the source code to the jar as well. You might need it for debugging. 😈
98+
99+
```
100+
mvn source:jar install
101+
```
102+
103+
## Adjust the model of the target application
104+
105+
Clone the Incidents App for Java: https://github.com/recap-conf/incidents-app-java
106+
107+
Create a `criticality.cds file` in the `db` module and paste the following content:
108+
109+
```cds
110+
using {sap.capire.incidents as my} from './schema';
111+
112+
annotate my.Status with {
113+
code @criticality {
114+
new @criticality.Neutral;
115+
assigned @criticality.Critical;
116+
in_process @criticality.Critical;
117+
on_hold @criticality.Negative;
118+
resolved @criticality.Positive;
119+
closed @criticality.Positive;
120+
};
121+
};
122+
123+
extend my.Urgency {
124+
criticality : Integer;
125+
};
126+
127+
annotate my.Urgency with {
128+
code @criticality {
129+
high @criticality.Negative;
130+
medium @criticality.Critical;
131+
};
132+
};
133+
```
134+
135+
Add the dependency of the just created plugin to your `srv/pom.xml`:
136+
137+
```xml
138+
<dependency>
139+
<groupId>com.sap.capire</groupId>
140+
<artifactId>criticality</artifactId>
141+
<version>1.0-SNAPSHOT</version>
142+
</dependency>
143+
```
144+
145+
Start the application and execute some HTTP requests:
146+
147+
```http
148+
GET http://localhost:8080/odata/v4/ProcessorService/Urgency
149+
Authorization: basic YWxpY2U6
150+
151+
###
152+
153+
GET http://localhost:8080/odata/v4/ProcessorService/Incidents?$expand=urgency
154+
Authorization: basic YWxpY2U6
155+
```
Lines changed: 16 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,16 @@
1+
package com.sap.capire;
2+
3+
import org.springframework.boot.autoconfigure.AutoConfiguration;
4+
import org.springframework.boot.autoconfigure.condition.ConditionalOnMissingBean;
5+
import org.springframework.context.annotation.Bean;
6+
7+
@AutoConfiguration
8+
public class AutoConfig {
9+
10+
@Bean
11+
@ConditionalOnMissingBean
12+
public CriticalityHandler someService() {
13+
return new CriticalityHandler();
14+
}
15+
16+
}
Lines changed: 102 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,102 @@
1+
package com.sap.capire;
2+
3+
import com.sap.cds.CdsData;
4+
import com.sap.cds.Result;
5+
6+
import com.sap.cds.reflect.CdsAssociationType;
7+
import com.sap.cds.reflect.CdsElement;
8+
import com.sap.cds.reflect.CdsEnumType;
9+
import com.sap.cds.reflect.CdsType;
10+
import com.sap.cds.services.cds.ApplicationService;
11+
import com.sap.cds.services.cds.CdsReadEventContext;
12+
import com.sap.cds.services.handler.EventHandler;
13+
import com.sap.cds.services.handler.annotations.After;
14+
import com.sap.cds.services.handler.annotations.ServiceName;
15+
16+
import java.util.Collection;
17+
import java.util.HashMap;
18+
import java.util.List;
19+
import java.util.Map;
20+
21+
22+
@ServiceName(value = "*", type = ApplicationService.class)
23+
public class CriticalityHandler implements EventHandler {
24+
25+
public static final String CRITICALITY_ELEMENT = "criticality";
26+
private final Map<String, Integer> criticalityMap;
27+
28+
public CriticalityHandler() {
29+
this.criticalityMap = new HashMap<>();
30+
this.criticalityMap.put("criticality.VeryNegative", -1);
31+
this.criticalityMap.put("criticality.Neutral", 0);
32+
this.criticalityMap.put("criticality.Negative", 1);
33+
this.criticalityMap.put("criticality.Critical", 2);
34+
this.criticalityMap.put("criticality.Positive", 3);
35+
this.criticalityMap.put("criticality.VeryPositive", 4);
36+
this.criticalityMap.put("criticality.Information", 5);
37+
}
38+
39+
@After
40+
public void handleCriticality(CdsReadEventContext ctx) {
41+
42+
ctx.getModel().entities().filter(
43+
cdsEntity -> cdsEntity.getQualifiedName().equalsIgnoreCase(ctx.getTarget().getQualifiedName())
44+
).findFirst().ifPresent(cdsEntity -> cdsEntity.elements()
45+
.filter(cdsElement -> cdsElement.getType().isEnum() || cdsElement.getType().isAssociation())
46+
.forEach(cdsElement -> processResultForCriticalityAnnotatedElement(cdsElement, ctx.getResult())));
47+
}
48+
49+
private void processResultForCriticalityAnnotatedElement(CdsElement cdsElement, Result result) {
50+
result.list().forEach(row -> processRow(cdsElement, row));
51+
}
52+
53+
private void processRow(CdsElement cdsElement, CdsData row) {
54+
if (cdsElement.getType().isEnum()) {
55+
processEnumElement(cdsElement, row);
56+
} else if (cdsElement.getType().isAssociation() && row.containsKey(cdsElement.getName())) {
57+
processExpandedEnumAssociation(cdsElement, row);
58+
}
59+
60+
}
61+
62+
@SuppressWarnings("unchecked")
63+
private void processExpandedEnumAssociation(CdsElement cdsElement, CdsData row) {
64+
((CdsAssociationType) cdsElement.getType()).getTarget().elements().filter(innerCdsElement -> innerCdsElement.getType().isEnum()).findFirst().ifPresent(innerCdsElement -> {
65+
if (isToManyAssoc(cdsElement)) {
66+
((List<CdsData>) row.get(cdsElement.getName())).forEach(innerRow -> processRow(innerCdsElement, innerRow));
67+
}
68+
else {
69+
processRow(innerCdsElement, (CdsData) row.get(cdsElement.getName()));
70+
}
71+
});
72+
}
73+
74+
private void processEnumElement(CdsElement cdsElement, CdsData row) {
75+
if (row.containsKey(cdsElement.getName())) {
76+
Map<String, Integer> criticalityValues = getCriticalityValues(cdsElement.getType());
77+
if (!criticalityValues.isEmpty()) {
78+
String value = (String) row.get(cdsElement.getName());
79+
if (criticalityValues.containsKey(value)) {
80+
row.put(CRITICALITY_ELEMENT, criticalityValues.get(value));
81+
}
82+
}
83+
}
84+
}
85+
86+
private static boolean isToManyAssoc(CdsElement cdsElement) {
87+
return ((CdsAssociationType) cdsElement.getType()).getCardinality().getTargetMax().equals("*");
88+
}
89+
90+
@SuppressWarnings("unchecked")
91+
private Map<String, Integer> getCriticalityValues(CdsType type) {
92+
final Map<String, Integer> valueCriticalityMap = new HashMap<>();
93+
Collection<CdsEnumType.Enumeral<String>> values = ((CdsEnumType<String>) type).enumerals().values();
94+
95+
for (CdsEnumType.Enumeral<String> value : values) {
96+
value.annotations().filter(cdsAnnotation -> cdsAnnotation.getName().startsWith("criticality.")).findFirst().ifPresent(cdsAnnotation ->
97+
valueCriticalityMap.put(value.value(), this.criticalityMap.get(cdsAnnotation.getName()))
98+
);
99+
}
100+
return valueCriticalityMap;
101+
}
102+
}
Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1 @@
1+
com.sap.capire.AutoConfig

0 commit comments

Comments
 (0)