Skip to content

Commit 0d6eb35

Browse files
committed
KOGITO-6685 Changed to configure Circuit Breaker using MicroProfile properties
Signed-off-by: Helber Belmiro <[email protected]>
1 parent 8630741 commit 0d6eb35

File tree

14 files changed

+167
-392
lines changed

14 files changed

+167
-392
lines changed

README.md

Lines changed: 32 additions & 24 deletions
Original file line numberDiff line numberDiff line change
@@ -154,8 +154,8 @@ The API Key scheme has an additional property that requires where to add the API
154154

155155
## Circuit Breaker
156156

157-
You can add [CircuitBreaker annotation from MicroProfile Fault Tolerance](https://microprofile.io/project/eclipse/microprofile-fault-tolerance/spec/src/main/asciidoc/circuitbreaker.asciidoc)
158-
to your generated classes by defining the desired configuration in `application.properties`.
157+
You can define the [CircuitBreaker annotation from MicroProfile Fault Tolerance](https://microprofile.io/project/eclipse/microprofile-fault-tolerance/spec/src/main/asciidoc/circuitbreaker.asciidoc)
158+
in your generated classes by setting the desired configuration in `application.properties`.
159159

160160
Let's say you have the following OpenAPI definition:
161161
````json
@@ -182,7 +182,6 @@ Let's say you have the following OpenAPI definition:
182182
}
183183
}
184184
},
185-
186185
"/bye": {
187186
"get": {
188187
"responses": {
@@ -217,39 +216,35 @@ Add the [SmallRye Fault Tolerance extension](https://quarkus.io/guides/smallrye-
217216
Assuming your Open API spec file is in `src/main/openapi/simple-openapi.json`, add the following configuration to your `application.properties` file:
218217

219218
````properties
220-
quarkus.openapi-generator.spec."simple-openapi.json".base-package=org.acme.openapi.simple
221-
222-
# Enables the CircuitBreaker extension
223-
quarkus.openapi-generator.CircuitBreaker.enabled=true
224-
225-
# Defines the configuration for the GET method to the /bye endpoint
226-
quarkus.openapi-generator.spec."simple-openapi.json"/byeGet/CircuitBreaker/failOn = java.lang.IllegalArgumentException,java.lang.NullPointerException
227-
quarkus.openapi-generator.spec."simple-openapi.json"/byeGet/CircuitBreaker/skipOn = java.lang.NumberFormatException
228-
quarkus.openapi-generator.spec."simple-openapi.json"/byeGet/CircuitBreaker/delay = 33
229-
quarkus.openapi-generator.spec."simple-openapi.json"/byeGet/CircuitBreaker/delayUnit = MILLIS
230-
quarkus.openapi-generator.spec."simple-openapi.json"/byeGet/CircuitBreaker/requestVolumeThreshold = 42
231-
quarkus.openapi-generator.spec."simple-openapi.json"/byeGet/CircuitBreaker/failureRatio = 3.14
232-
quarkus.openapi-generator.spec."simple-openapi.json"/byeGet/CircuitBreaker/successThreshold = 22
219+
quarkus.openapi-generator.codegen.spec."simple-openapi.json".base-package=org.acme.openapi.simple
220+
221+
# Enables the CircuitBreaker extension for the byeGet method from the DefaultApi class
222+
org.acme.openapi.simple.api.DefaultApi/byeGet/CircuitBreaker/enabled=true
233223
````
234224

235225
With the above configuration, your Rest Clients will be created with a code similar to the following:
236226

237227
````java
228+
package org.acme.openapi.simple.api;
229+
230+
import org.eclipse.microprofile.rest.client.inject.RegisterRestClient;
231+
232+
import java.io.InputStream;
233+
import java.io.OutputStream;
234+
import java.util.List;
235+
import java.util.Map;
236+
import javax.ws.rs.*;
237+
import javax.ws.rs.core.Response;
238+
import javax.ws.rs.core.MediaType;
239+
238240
@Path("")
239241
@RegisterRestClient
240242
public interface DefaultApi {
241243

242244
@GET
243245
@Path("/bye")
244246
@Produces({"text/plain"})
245-
@org.eclipse.microprofile.faulttolerance.CircuitBreaker(
246-
delay = 33,
247-
delayUnit = java.time.temporal.ChronoUnit.MILLIS,
248-
failOn = { java.lang.IllegalArgumentException.class, java.lang.NullPointerException.class },
249-
failureRatio = 3.14,
250-
requestVolumeThreshold = 42,
251-
skipOn = java.lang.NumberFormatException.class,
252-
successThreshold = 22)
247+
@org.eclipse.microprofile.faulttolerance.CircuitBreaker
253248
public String byeGet();
254249

255250
@GET
@@ -260,6 +255,19 @@ public interface DefaultApi {
260255
}
261256
````
262257

258+
You can also override the default Circuit Breaker configuration by setting the properties in `application.properties` [just as you would for a traditional MicroProfile application](https://quarkus.io/guides/smallrye-fault-tolerance#runtime-configuration):
259+
260+
````properties
261+
org.acme.openapi.simple.api.DefaultApi/byeGet/CircuitBreaker/failOn = java.lang.IllegalArgumentException,java.lang.NullPointerException
262+
org.acme.openapi.simple.api.DefaultApi/byeGet/CircuitBreaker/skipOn = java.lang.NumberFormatException
263+
org.acme.openapi.simple.api.DefaultApi/byeGet/CircuitBreaker/delay = 33
264+
org.acme.openapi.simple.api.DefaultApi/byeGet/CircuitBreaker/delayUnit = MILLIS
265+
org.acme.openapi.simple.api.DefaultApi/byeGet/CircuitBreaker/requestVolumeThreshold = 42
266+
org.acme.openapi.simple.api.DefaultApi/byeGet/CircuitBreaker/failureRatio = 3.14
267+
org.acme.openapi.simple.api.DefaultApi/byeGet/CircuitBreaker/successThreshold = 22
268+
````
269+
270+
263271
## Known Limitations
264272

265273
These are the known limitations of this pre-release version:
Lines changed: 20 additions & 82 deletions
Original file line numberDiff line numberDiff line change
@@ -1,105 +1,43 @@
11
package io.quarkiverse.openapi.generator.deployment.circuitbreaker;
22

33
import java.util.List;
4-
import java.util.Map;
5-
import java.util.Objects;
6-
import java.util.stream.Collectors;
7-
import java.util.stream.Stream;
84

95
public final class CircuitBreakerConfiguration {
106

11-
private static final CircuitBreakerConfiguration EMPTY = CircuitBreakerConfiguration.builder()
12-
.enabled(false)
13-
.operations(List.of())
14-
.build();
7+
private final List<CircuitBreakerClassConfiguration> classConfigurations;
158

16-
private final Boolean enabled;
17-
18-
private final List<Operation> operations;
19-
20-
private CircuitBreakerConfiguration(Builder builder) {
21-
enabled = Objects.requireNonNull(builder.enabled);
22-
operations = Objects.requireNonNull(builder.operations);
9+
public CircuitBreakerConfiguration(List<CircuitBreakerClassConfiguration> classConfigurations) {
10+
this.classConfigurations = List.copyOf(classConfigurations);
2311
}
2412

25-
public Boolean getEnabled() {
26-
return enabled;
13+
public List<CircuitBreakerClassConfiguration> getClassConfigurations() {
14+
return classConfigurations;
2715
}
2816

29-
public List<Operation> getOperations() {
30-
return operations;
17+
CircuitBreakerClassConfiguration getClassConfiguration(String className) {
18+
return getClassConfigurations().stream()
19+
.filter(c -> c.getClassName().equals(className))
20+
.findFirst()
21+
.orElseThrow(() -> new IllegalArgumentException("Configuration not found for class: " + className));
3122
}
3223

33-
public static CircuitBreakerConfiguration empty() {
34-
return EMPTY;
35-
}
36-
37-
public static Builder builder() {
38-
return new Builder();
39-
}
24+
public static final class CircuitBreakerClassConfiguration {
4025

41-
public static final class Operation {
42-
private final String name;
43-
private final Map<String, String> attributes;
44-
45-
public Operation(String name, Map<String, String> attributes) {
46-
this.name = Objects.requireNonNull(name);
47-
this.attributes = Objects.requireNonNull(attributes);
48-
}
49-
50-
public String getName() {
51-
return name;
52-
}
26+
private final String className;
5327

54-
public Map<String, String> getAttributes() {
55-
return attributes;
56-
}
57-
58-
public String getAttributesAsString() {
59-
return attributes.entrySet().stream()
60-
.sorted(Map.Entry.comparingByKey())
61-
.map(entry -> {
62-
switch (entry.getKey()) {
63-
case "failOn":
64-
case "skipOn":
65-
List<String> classes = Stream.of(entry.getValue().split(","))
66-
.map(String::trim)
67-
.map(value -> value + ".class")
68-
.collect(Collectors.toUnmodifiableList());
69-
70-
if (classes.size() == 1) {
71-
return entry.getKey() + " = " + classes.get(0);
72-
} else {
73-
return entry.getKey() + " = { " + String.join(", ", classes) + " }";
74-
}
75-
case "delayUnit":
76-
return "delayUnit = java.time.temporal.ChronoUnit." + entry.getValue();
77-
default:
78-
return entry.getKey() + " = " + entry.getValue().trim();
79-
}
80-
}).collect(Collectors.joining(", "));
81-
}
82-
}
83-
84-
public static final class Builder {
85-
private List<Operation> operations;
86-
private Boolean enabled;
87-
88-
private Builder() {
89-
}
28+
private final List<String> methods;
9029

91-
public Builder enabled(boolean enabled) {
92-
this.enabled = enabled;
93-
return this;
30+
public CircuitBreakerClassConfiguration(String className, List<String> methods) {
31+
this.className = className;
32+
this.methods = List.copyOf(methods);
9433
}
9534

96-
public Builder operations(List<Operation> operations) {
97-
this.operations = List.copyOf(operations);
98-
return this;
35+
public String getClassName() {
36+
return className;
9937
}
10038

101-
public CircuitBreakerConfiguration build() {
102-
return new CircuitBreakerConfiguration(this);
39+
public List<String> getMethods() {
40+
return methods;
10341
}
10442
}
10543
}
Lines changed: 26 additions & 41 deletions
Original file line numberDiff line numberDiff line change
@@ -1,74 +1,59 @@
11
package io.quarkiverse.openapi.generator.deployment.circuitbreaker;
22

33
import java.util.Collection;
4-
import java.util.HashMap;
54
import java.util.List;
6-
import java.util.Map;
5+
import java.util.Set;
76
import java.util.function.UnaryOperator;
87
import java.util.stream.Collectors;
98

10-
public final class CircuitBreakerConfigurationParser {
11-
12-
private static final String CONFIG_PREFIX = "quarkus.openapi-generator.spec.";
13-
14-
private static final String PROPERTY_REGEX = CONFIG_PREFIX + "\".+\"/.+/CircuitBreaker/.+";
9+
import io.quarkiverse.openapi.generator.deployment.circuitbreaker.CircuitBreakerConfiguration.CircuitBreakerClassConfiguration;
1510

16-
private static final String CIRCUIT_BREAKER_ENABLED_PROPERTY_NAME = "quarkus.openapi-generator.CircuitBreaker.enabled";
11+
public final class CircuitBreakerConfigurationParser {
1712

1813
private final UnaryOperator<String> nameToValuePropertyMapper;
1914

20-
private final String openApiFileName;
21-
22-
private final int operationIndex;
23-
24-
public CircuitBreakerConfigurationParser(String openApiFileName, UnaryOperator<String> nameToValuePropertyMapper) {
25-
this.openApiFileName = openApiFileName;
15+
public CircuitBreakerConfigurationParser(UnaryOperator<String> nameToValuePropertyMapper) {
2616
this.nameToValuePropertyMapper = nameToValuePropertyMapper;
27-
operationIndex = CONFIG_PREFIX.length() + openApiFileName.length() + 3;
2817
}
2918

3019
public CircuitBreakerConfiguration parse(Collection<String> propertyNames) {
31-
if (propertyNames.contains(CIRCUIT_BREAKER_ENABLED_PROPERTY_NAME)
32-
&& Boolean.parseBoolean(nameToValuePropertyMapper.apply(CIRCUIT_BREAKER_ENABLED_PROPERTY_NAME))) {
33-
return CircuitBreakerConfiguration.builder()
34-
.enabled(true)
35-
.operations(getOperations(propertyNames))
36-
.build();
37-
} else {
38-
return CircuitBreakerConfiguration.empty();
39-
}
20+
return new CircuitBreakerConfiguration(readClasses(propertyNames));
4021
}
4122

42-
private List<CircuitBreakerConfiguration.Operation> getOperations(Collection<String> propertyNames) {
43-
Map<String, Map<String, String>> operationsMap = new HashMap<>();
23+
private List<CircuitBreakerClassConfiguration> readClasses(Collection<String> propertyNames) {
24+
List<String> filteredPropertyNames = filterPropertyNames(propertyNames).stream()
25+
.filter(property -> nameToValuePropertyMapper.apply(property).equals("true"))
26+
.collect(Collectors.toList());
4427

45-
for (String propertyName : filterPropertyNames(propertyNames)) {
46-
String operationName = getOperationName(propertyName);
47-
String circuitBreakerAttributeName = getCircuitBreakerAttributeName(propertyName);
48-
String circuitBreakerAttributeValue = nameToValuePropertyMapper.apply(propertyName).trim();
28+
Set<String> classNames = filteredPropertyNames.stream()
29+
.map(this::getClassName)
30+
.collect(Collectors.toSet());
4931

50-
operationsMap.computeIfAbsent(operationName, k -> new HashMap<>())
51-
.put(circuitBreakerAttributeName, circuitBreakerAttributeValue);
52-
}
32+
return classNames.stream()
33+
.map(className -> new CircuitBreakerClassConfiguration(className,
34+
getMethodNames(className, filteredPropertyNames)))
35+
.collect(Collectors.toUnmodifiableList());
36+
}
5337

54-
return operationsMap.entrySet().stream()
55-
.map(entry -> new CircuitBreakerConfiguration.Operation(entry.getKey(), entry.getValue()))
38+
private List<String> getMethodNames(String className, List<String> propertyNames) {
39+
return propertyNames.stream()
40+
.filter(propertyName -> propertyName.startsWith(className + "/"))
41+
.map(this::getMethodName)
5642
.collect(Collectors.toUnmodifiableList());
5743
}
5844

59-
private String getCircuitBreakerAttributeName(String propertyName) {
60-
return propertyName.substring(propertyName.lastIndexOf("/") + 1);
45+
private String getClassName(String propertyName) {
46+
return propertyName.substring(0, propertyName.indexOf("/"));
6147
}
6248

63-
private String getOperationName(String propertyName) {
64-
return propertyName.substring(operationIndex, propertyName.indexOf("/CircuitBreaker/"));
49+
private String getMethodName(String propertyName) {
50+
return propertyName.substring(propertyName.indexOf("/") + 1, propertyName.indexOf("/CircuitBreaker/"));
6551
}
6652

6753
private List<String> filterPropertyNames(Collection<String> propertyNames) {
6854
return propertyNames.stream()
69-
.filter(propertyName -> propertyName.matches(PROPERTY_REGEX))
7055
.filter(propertyName -> propertyName
71-
.matches(CONFIG_PREFIX + "\"" + openApiFileName + "\"/.+/CircuitBreaker/.+"))
56+
.matches(".+/.+/CircuitBreaker/enabled"))
7257
.collect(Collectors.toList());
7358
}
7459
}

deployment/src/main/java/io/quarkiverse/openapi/generator/deployment/codegen/OpenApiGeneratorCodeGenBase.java

Lines changed: 4 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -48,9 +48,8 @@ public boolean trigger(CodeGenContext context) throws CodeGenException {
4848
.filter(s -> s.endsWith(this.inputExtension()))
4949
.map(Path::of).forEach(openApiFilePath -> {
5050
final CircuitBreakerConfiguration circuitBreakerConfiguration = getCircuitBreakerConfiguration(
51-
context,
52-
openApiFilePath);
53-
51+
context);
52+
5453
final OpenApiClientGeneratorWrapper generator = new OpenApiClientGeneratorWrapper(
5554
openApiFilePath.normalize(), outDir)
5655
.withCircuitBreakerConfiguration(circuitBreakerConfiguration);
@@ -69,11 +68,11 @@ public boolean trigger(CodeGenContext context) throws CodeGenException {
6968
return false;
7069
}
7170

72-
private CircuitBreakerConfiguration getCircuitBreakerConfiguration(CodeGenContext context, Path openApiFilePath) {
71+
private CircuitBreakerConfiguration getCircuitBreakerConfiguration(CodeGenContext context) {
7372
UnaryOperator<String> nameToValuePropertyMapper = propertyName -> context.config().getValue(propertyName,
7473
String.class);
7574

76-
return new CircuitBreakerConfigurationParser(openApiFilePath.toFile().getName(), nameToValuePropertyMapper)
75+
return new CircuitBreakerConfigurationParser(nameToValuePropertyMapper)
7776
.parse(getConfigPropertyNames(context));
7877
}
7978

deployment/src/main/resources/templates/api.qute

Lines changed: 5 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -53,9 +53,11 @@ public interface {classname} {
5353
{#if op.hasProduces}
5454
@Produces(\{{#for produce in op.produces}"{produce.mediaType}"{#if produce_hasNext}, {/if}{/for}\})
5555
{/if}
56-
{#if circuit-breaker.enabled}{#for cbOperationConfig in circuit-breaker.operations}{#if cbOperationConfig.name == op.nickname}
57-
@org.eclipse.microprofile.faulttolerance.CircuitBreaker({cbOperationConfig.attributesAsString})
58-
{/if}{/for}{/if}
56+
{#for cbClassConfig in circuit-breaker.classConfigurations.orEmpty}{#if cbClassConfig.className == package + classname}
57+
{#for cbMethod in cbClassConfig.methods.orEmpty}{#if cbMethod == op.nickname}
58+
@org.eclipse.microprofile.faulttolerance.CircuitBreaker
59+
{/if}{/for}
60+
{/if}{/for}
5961
public {#if op.returnType}{op.returnType}{#else}void{/if} {op.nickname}({#for p in op.allParams}{#include pathParams.qute param=p/}{#include queryParams.qute param=p/}{#include bodyParams.qute param=p/}{#include formParams.qute param=p/}{#include headerParams.qute param=p/}{#if p_hasNext}, {/if}{/for});
6062

6163
{/for}

0 commit comments

Comments
 (0)