Skip to content

Commit a33ef58

Browse files
feat: ofrep provider (#1429)
Signed-off-by: Rahul Baradol <[email protected]> Signed-off-by: Todd Baert <[email protected]> Co-authored-by: Todd Baert <[email protected]>
1 parent f1d6b50 commit a33ef58

File tree

18 files changed

+1521
-0
lines changed

18 files changed

+1521
-0
lines changed

.github/component_owners.yml

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -37,6 +37,9 @@ components:
3737
- liran2000
3838
providers/multiprovider:
3939
- liran2000
40+
providers/ofrep-provider:
41+
- Rahul-Baradol
42+
- toddbaert
4043
tools/flagd-http-connector:
4144
- liran2000
4245

.release-please-manifest.json

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -10,6 +10,7 @@
1010
"providers/configcat": "0.2.1",
1111
"providers/statsig": "0.2.1",
1212
"providers/multiprovider": "0.0.3",
13+
"providers/ofrep": "0.0.1",
1314
"tools/junit-openfeature": "0.2.1",
1415
"tools/flagd-http-connector": "0.0.4",
1516
".": "1.0.0"

pom.xml

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -40,6 +40,7 @@
4040
<module>providers/configcat</module>
4141
<module>providers/statsig</module>
4242
<module>providers/multiprovider</module>
43+
<module>providers/ofrep</module>
4344
<module>tools/flagd-http-connector</module>
4445
</modules>
4546

providers/ofrep/README.md

Lines changed: 74 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,74 @@
1+
# OFREP Provider for OpenFeature
2+
3+
This provider allows to connect to any feature flag management system that supports OFREP.
4+
5+
## Installation
6+
For Maven
7+
<!-- x-release-please-start-version -->
8+
```xml
9+
<dependency>
10+
<groupId>dev.openfeature.contrib.providers</groupId>
11+
<artifactId>ofrep</artifactId>
12+
<version>0.0.1</version>
13+
</dependency>
14+
```
15+
16+
For Gradle
17+
```groovy
18+
implementation 'dev.openfeature.contrib.providers:ofrep:0.0.1'
19+
```
20+
<!-- x-release-please-end-version -->
21+
22+
## Configuration and Usage
23+
24+
### Usage
25+
```java
26+
OfrepProviderOptions options = OfrepProviderOptions.builder().build();
27+
OfrepProvider ofrepProvider = OfrepProvider.constructProvider(options);
28+
```
29+
### Example
30+
```java
31+
import dev.openfeature.contrib.providers.ofrep.OfrepProvider;
32+
import dev.openfeature.contrib.providers.ofrep.OfrepProviderOptions;
33+
import dev.openfeature.sdk.Client;
34+
import dev.openfeature.sdk.FlagEvaluationDetails;
35+
import dev.openfeature.sdk.MutableContext;
36+
import dev.openfeature.sdk.OpenFeatureAPI;
37+
38+
public class App {
39+
public static void main(String[] args) {
40+
OpenFeatureAPI openFeatureAPI = OpenFeatureAPI.getInstance();
41+
42+
OfrepProviderOptions options = OfrepProviderOptions.builder().build();
43+
OfrepProvider ofrepProvider = OfrepProvider.constructProvider(options);
44+
45+
openFeatureAPI.setProvider(ofrepProvider);
46+
47+
Client client = openFeatureAPI.getClient();
48+
49+
MutableContext context = new MutableContext();
50+
context.setTargetingKey("my-identify-id");
51+
52+
FlagEvaluationDetails<Boolean> details = client.getBooleanDetails("my-boolean-flag", false, context);
53+
System.out.println("Flag value: " + details.getValue());
54+
55+
openFeatureAPI.shutdown();
56+
}
57+
}
58+
```
59+
60+
### Configuration options
61+
62+
Options are passed via `OfrepProviderOptions`, using which default values can be overridden.
63+
64+
Given below are the supported configurations:
65+
66+
67+
| Option name | Type | Default | Description
68+
| ----------- | ------- | --------- | ---------
69+
| baseUrl | String | http://localhost:8016 | Override the default OFREP API URL.
70+
| headers | ImmutableMap | Empty Map | Add custom headers which will be sent with each network request to the OFREP API.
71+
| timeout | Duration | 10 Seconds | The timeout duration to establishing the connection.
72+
| proxySelector | ProxySelector | ProxySelector.getDefault() | The proxy selector used by HTTP Client.
73+
| executor | Executor | Thread Pool of size 5 | The executor used by HTTP Client.
74+

providers/ofrep/pom.xml

Lines changed: 85 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,85 @@
1+
<?xml version="1.0"?>
2+
<project xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 https://maven.apache.org/xsd/maven-4.0.0.xsd" xmlns="http://maven.apache.org/POM/4.0.0"
3+
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance">
4+
<modelVersion>4.0.0</modelVersion>
5+
<parent>
6+
<groupId>dev.openfeature.contrib</groupId>
7+
<artifactId>parent</artifactId>
8+
<version>1.0.0</version>
9+
<relativePath>../../pom.xml</relativePath>
10+
</parent>
11+
12+
<groupId>dev.openfeature.contrib.providers</groupId>
13+
<artifactId>ofrep</artifactId>
14+
<version>0.0.1</version> <!--x-release-please-version -->
15+
16+
<name>ofrep</name>
17+
<description>OFREP Provider</description>
18+
<url>https://openfeature.dev</url>
19+
20+
<developers>
21+
<developer>
22+
<id>Rahul-Baradol</id>
23+
<name>Rahul Baradol</name>
24+
<organization>OpenFeature</organization>
25+
<url>https://openfeature.dev/</url>
26+
</developer>
27+
</developers>
28+
29+
<dependencies>
30+
<dependency>
31+
<groupId>org.junit.jupiter</groupId>
32+
<artifactId>junit-jupiter</artifactId>
33+
<scope>test</scope>
34+
</dependency>
35+
36+
<dependency>
37+
<groupId>com.fasterxml.jackson.datatype</groupId>
38+
<artifactId>jackson-datatype-jsr310</artifactId>
39+
<version>2.19.1</version>
40+
</dependency>
41+
42+
<dependency>
43+
<groupId>com.fasterxml.jackson.core</groupId>
44+
<artifactId>jackson-core</artifactId>
45+
<version>2.19.1</version>
46+
</dependency>
47+
48+
<dependency>
49+
<groupId>com.fasterxml.jackson.core</groupId>
50+
<artifactId>jackson-databind</artifactId>
51+
<version>2.19.1</version>
52+
</dependency>
53+
54+
<dependency>
55+
<groupId>com.fasterxml.jackson.core</groupId>
56+
<artifactId>jackson-annotations</artifactId>
57+
<version>2.19.1</version>
58+
</dependency>
59+
60+
<dependency>
61+
<groupId>org.slf4j</groupId>
62+
<artifactId>slf4j-api</artifactId>
63+
<version>2.0.17</version>
64+
</dependency>
65+
66+
<dependency>
67+
<groupId>commons-validator</groupId>
68+
<artifactId>commons-validator</artifactId>
69+
<version>1.7</version>
70+
</dependency>
71+
72+
<dependency>
73+
<groupId>com.google.guava</groupId>
74+
<artifactId>guava</artifactId>
75+
<version>33.4.0-jre</version>
76+
</dependency>
77+
78+
<dependency>
79+
<groupId>com.squareup.okhttp3</groupId>
80+
<artifactId>mockwebserver</artifactId>
81+
<version>4.12.0</version>
82+
<scope>test</scope>
83+
</dependency>
84+
</dependencies>
85+
</project>
Lines changed: 139 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,139 @@
1+
package dev.openfeature.contrib.providers.ofrep;
2+
3+
import dev.openfeature.contrib.providers.ofrep.internal.Resolver;
4+
import dev.openfeature.sdk.EvaluationContext;
5+
import dev.openfeature.sdk.FeatureProvider;
6+
import dev.openfeature.sdk.Metadata;
7+
import dev.openfeature.sdk.ProviderEvaluation;
8+
import dev.openfeature.sdk.Value;
9+
import java.util.concurrent.Executor;
10+
import java.util.concurrent.ExecutorService;
11+
import java.util.concurrent.TimeUnit;
12+
import lombok.extern.slf4j.Slf4j;
13+
import org.apache.commons.validator.routines.UrlValidator;
14+
15+
/**
16+
* OpenFeature provider for OFREP.
17+
*/
18+
@Slf4j
19+
public final class OfrepProvider implements FeatureProvider {
20+
21+
private static final String OFREP_PROVIDER = "ofrep";
22+
private static final long DEFAULT_EXECUTOR_SHUTDOWN_TIMEOUT = 1000;
23+
24+
private final Resolver ofrepResolver;
25+
private Executor executor;
26+
27+
public static OfrepProvider constructProvider() {
28+
return new OfrepProvider();
29+
}
30+
31+
/**
32+
* Constructs an OfrepProvider with the specified options.
33+
*
34+
* @param options The options for configuring the provider.
35+
* @return An instance of OfrepProvider configured with the provided options.
36+
* @throws IllegalArgumentException if any of the options are invalid.
37+
*/
38+
public static OfrepProvider constructProvider(OfrepProviderOptions options) {
39+
if (!isValidUrl(options.getBaseUrl())) {
40+
throw new IllegalArgumentException("Invalid base URL: " + options.getBaseUrl());
41+
}
42+
43+
if (options.getHeaders() == null) {
44+
throw new IllegalArgumentException("Headers cannot be null");
45+
}
46+
47+
if (options.getRequestTimeout() == null
48+
|| options.getRequestTimeout().isNegative()
49+
|| options.getRequestTimeout().isZero()) {
50+
throw new IllegalArgumentException("Request timeout must be a positive duration");
51+
}
52+
53+
if (options.getConnectTimeout() == null
54+
|| options.getConnectTimeout().isNegative()
55+
|| options.getConnectTimeout().isZero()) {
56+
throw new IllegalArgumentException("Connect timeout must be a positive duration");
57+
}
58+
59+
if (options.getProxySelector() == null) {
60+
throw new IllegalArgumentException("ProxySelector cannot be null");
61+
}
62+
63+
if (options.getExecutor() == null) {
64+
throw new IllegalArgumentException("Executor cannot be null");
65+
}
66+
67+
return new OfrepProvider(options);
68+
}
69+
70+
private OfrepProvider() {
71+
this(new OfrepProviderOptions.Builder().build());
72+
}
73+
74+
private OfrepProvider(OfrepProviderOptions options) {
75+
this.executor = options.getExecutor();
76+
this.ofrepResolver = new Resolver(
77+
options.getBaseUrl(),
78+
options.getHeaders(),
79+
options.getRequestTimeout(),
80+
options.getConnectTimeout(),
81+
options.getProxySelector(),
82+
options.getExecutor());
83+
}
84+
85+
@Override
86+
public Metadata getMetadata() {
87+
return () -> OFREP_PROVIDER;
88+
}
89+
90+
@Override
91+
public void shutdown() {
92+
if (executor instanceof ExecutorService) {
93+
ExecutorService executorService = (ExecutorService) executor;
94+
try {
95+
executorService.shutdown();
96+
97+
if (!executorService.awaitTermination(DEFAULT_EXECUTOR_SHUTDOWN_TIMEOUT, TimeUnit.MILLISECONDS)) {
98+
executorService.shutdownNow();
99+
if (!executorService.awaitTermination(DEFAULT_EXECUTOR_SHUTDOWN_TIMEOUT, TimeUnit.MILLISECONDS)) {
100+
log.error("Provider couldn't shutdown gracefully.");
101+
}
102+
}
103+
} catch (InterruptedException e) {
104+
executorService.shutdownNow();
105+
Thread.currentThread().interrupt();
106+
}
107+
}
108+
}
109+
110+
@Override
111+
public ProviderEvaluation<Boolean> getBooleanEvaluation(String key, Boolean defaultValue, EvaluationContext ctx) {
112+
return ofrepResolver.resolveBoolean(key, defaultValue, ctx);
113+
}
114+
115+
@Override
116+
public ProviderEvaluation<String> getStringEvaluation(String key, String defaultValue, EvaluationContext ctx) {
117+
return ofrepResolver.resolveString(key, defaultValue, ctx);
118+
}
119+
120+
@Override
121+
public ProviderEvaluation<Integer> getIntegerEvaluation(String key, Integer defaultValue, EvaluationContext ctx) {
122+
return ofrepResolver.resolveInteger(key, defaultValue, ctx);
123+
}
124+
125+
@Override
126+
public ProviderEvaluation<Double> getDoubleEvaluation(String key, Double defaultValue, EvaluationContext ctx) {
127+
return ofrepResolver.resolveDouble(key, defaultValue, ctx);
128+
}
129+
130+
@Override
131+
public ProviderEvaluation<Value> getObjectEvaluation(String key, Value defaultValue, EvaluationContext ctx) {
132+
return ofrepResolver.resolveObject(key, defaultValue, ctx);
133+
}
134+
135+
private static boolean isValidUrl(String url) {
136+
UrlValidator validator = new UrlValidator(new String[] {"http", "https"}, UrlValidator.ALLOW_LOCAL_URLS);
137+
return validator.isValid(url);
138+
}
139+
}
Lines changed: 38 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,38 @@
1+
package dev.openfeature.contrib.providers.ofrep;
2+
3+
import com.google.common.collect.ImmutableList;
4+
import com.google.common.collect.ImmutableMap;
5+
import java.net.ProxySelector;
6+
import java.time.Duration;
7+
import java.util.concurrent.Executor;
8+
import java.util.concurrent.Executors;
9+
import lombok.Builder;
10+
import lombok.Getter;
11+
12+
/**
13+
* Options for configuring the OFREP provider.
14+
*/
15+
@Getter
16+
@Builder(builderClassName = "Builder", buildMethodName = "build")
17+
public class OfrepProviderOptions {
18+
19+
private static final int DEFAULT_THREAD_POOL_SIZE = 5;
20+
21+
@Builder.Default
22+
private final String baseUrl = "http://localhost:8016";
23+
24+
@Builder.Default
25+
private final ProxySelector proxySelector = ProxySelector.getDefault();
26+
27+
@Builder.Default
28+
private final Executor executor = Executors.newFixedThreadPool(DEFAULT_THREAD_POOL_SIZE);
29+
30+
@Builder.Default
31+
private final Duration requestTimeout = Duration.ofSeconds(10);
32+
33+
@Builder.Default
34+
private final Duration connectTimeout = Duration.ofSeconds(10);
35+
36+
@Builder.Default
37+
private final ImmutableMap<String, ImmutableList<String>> headers = ImmutableMap.of();
38+
}

0 commit comments

Comments
 (0)