-
Notifications
You must be signed in to change notification settings - Fork 55
feat: ofrep provider #1429
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Merged
+1,521
−0
Merged
feat: ofrep provider #1429
Changes from all commits
Commits
Show all changes
13 commits
Select commit
Hold shift + click to select a range
c18d670
feat: ofrep provider
Rahul-Baradol 6522460
test: unit tests for ofrep provider
Rahul-Baradol 771d330
fix: shutting down executor on provider shutdown
Rahul-Baradol 54491a8
fix: set default thread pool size for executor
Rahul-Baradol 7ca1e33
docs: configuration and usage
Rahul-Baradol b502c6e
feat: adds request timeout option for provider
Rahul-Baradol 4fa2c70
fix: ensures graceful provider shutdown
Rahul-Baradol 7650bbf
refactor: making ofrep request dto immutable
Rahul-Baradol 57ae473
Merge branch 'main' into ofrep-provider
toddbaert 3f22783
Update .release-please-manifest.json
toddbaert b1436bf
Update providers/ofrep/pom.xml
toddbaert 4c855aa
Update .github/component_owners.yml
toddbaert 69bb17f
Apply suggestions from code review
toddbaert File filter
Filter by extension
Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
There are no files selected for viewing
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,74 @@ | ||
# OFREP Provider for OpenFeature | ||
|
||
This provider allows to connect to any feature flag management system that supports OFREP. | ||
|
||
## Installation | ||
For Maven | ||
<!-- x-release-please-start-version --> | ||
```xml | ||
<dependency> | ||
<groupId>dev.openfeature.contrib.providers</groupId> | ||
<artifactId>ofrep</artifactId> | ||
<version>0.0.1</version> | ||
</dependency> | ||
``` | ||
|
||
For Gradle | ||
```groovy | ||
implementation 'dev.openfeature.contrib.providers:ofrep:0.0.1' | ||
``` | ||
<!-- x-release-please-end-version --> | ||
|
||
## Configuration and Usage | ||
|
||
### Usage | ||
```java | ||
OfrepProviderOptions options = OfrepProviderOptions.builder().build(); | ||
OfrepProvider ofrepProvider = OfrepProvider.constructProvider(options); | ||
``` | ||
### Example | ||
```java | ||
import dev.openfeature.contrib.providers.ofrep.OfrepProvider; | ||
import dev.openfeature.contrib.providers.ofrep.OfrepProviderOptions; | ||
import dev.openfeature.sdk.Client; | ||
import dev.openfeature.sdk.FlagEvaluationDetails; | ||
import dev.openfeature.sdk.MutableContext; | ||
import dev.openfeature.sdk.OpenFeatureAPI; | ||
|
||
public class App { | ||
public static void main(String[] args) { | ||
OpenFeatureAPI openFeatureAPI = OpenFeatureAPI.getInstance(); | ||
|
||
OfrepProviderOptions options = OfrepProviderOptions.builder().build(); | ||
OfrepProvider ofrepProvider = OfrepProvider.constructProvider(options); | ||
|
||
openFeatureAPI.setProvider(ofrepProvider); | ||
|
||
Client client = openFeatureAPI.getClient(); | ||
|
||
MutableContext context = new MutableContext(); | ||
context.setTargetingKey("my-identify-id"); | ||
|
||
FlagEvaluationDetails<Boolean> details = client.getBooleanDetails("my-boolean-flag", false, context); | ||
System.out.println("Flag value: " + details.getValue()); | ||
|
||
openFeatureAPI.shutdown(); | ||
} | ||
} | ||
``` | ||
|
||
### Configuration options | ||
|
||
Options are passed via `OfrepProviderOptions`, using which default values can be overridden. | ||
|
||
Given below are the supported configurations: | ||
|
||
|
||
| Option name | Type | Default | Description | ||
| ----------- | ------- | --------- | --------- | ||
| baseUrl | String | http://localhost:8016 | Override the default OFREP API URL. | ||
| headers | ImmutableMap | Empty Map | Add custom headers which will be sent with each network request to the OFREP API. | ||
| timeout | Duration | 10 Seconds | The timeout duration to establishing the connection. | ||
| proxySelector | ProxySelector | ProxySelector.getDefault() | The proxy selector used by HTTP Client. | ||
| executor | Executor | Thread Pool of size 5 | The executor used by HTTP Client. | ||
|
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,85 @@ | ||
<?xml version="1.0"?> | ||
<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" | ||
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"> | ||
<modelVersion>4.0.0</modelVersion> | ||
<parent> | ||
<groupId>dev.openfeature.contrib</groupId> | ||
<artifactId>parent</artifactId> | ||
<version>1.0.0</version> | ||
<relativePath>../../pom.xml</relativePath> | ||
</parent> | ||
|
||
<groupId>dev.openfeature.contrib.providers</groupId> | ||
<artifactId>ofrep</artifactId> | ||
<version>0.0.1</version> <!--x-release-please-version --> | ||
|
||
<name>ofrep</name> | ||
<description>OFREP Provider</description> | ||
<url>https://openfeature.dev</url> | ||
|
||
<developers> | ||
<developer> | ||
<id>Rahul-Baradol</id> | ||
<name>Rahul Baradol</name> | ||
<organization>OpenFeature</organization> | ||
<url>https://openfeature.dev/</url> | ||
</developer> | ||
</developers> | ||
|
||
<dependencies> | ||
<dependency> | ||
<groupId>org.junit.jupiter</groupId> | ||
<artifactId>junit-jupiter</artifactId> | ||
<scope>test</scope> | ||
</dependency> | ||
|
||
<dependency> | ||
<groupId>com.fasterxml.jackson.datatype</groupId> | ||
<artifactId>jackson-datatype-jsr310</artifactId> | ||
<version>2.19.1</version> | ||
</dependency> | ||
|
||
<dependency> | ||
<groupId>com.fasterxml.jackson.core</groupId> | ||
<artifactId>jackson-core</artifactId> | ||
<version>2.19.1</version> | ||
</dependency> | ||
|
||
<dependency> | ||
<groupId>com.fasterxml.jackson.core</groupId> | ||
<artifactId>jackson-databind</artifactId> | ||
<version>2.19.1</version> | ||
</dependency> | ||
|
||
<dependency> | ||
<groupId>com.fasterxml.jackson.core</groupId> | ||
<artifactId>jackson-annotations</artifactId> | ||
<version>2.19.1</version> | ||
</dependency> | ||
|
||
<dependency> | ||
<groupId>org.slf4j</groupId> | ||
<artifactId>slf4j-api</artifactId> | ||
<version>2.0.17</version> | ||
</dependency> | ||
|
||
<dependency> | ||
<groupId>commons-validator</groupId> | ||
<artifactId>commons-validator</artifactId> | ||
<version>1.7</version> | ||
</dependency> | ||
|
||
<dependency> | ||
<groupId>com.google.guava</groupId> | ||
<artifactId>guava</artifactId> | ||
<version>33.4.0-jre</version> | ||
</dependency> | ||
|
||
<dependency> | ||
<groupId>com.squareup.okhttp3</groupId> | ||
<artifactId>mockwebserver</artifactId> | ||
<version>4.12.0</version> | ||
<scope>test</scope> | ||
</dependency> | ||
</dependencies> | ||
</project> |
139 changes: 139 additions & 0 deletions
139
providers/ofrep/src/main/java/dev/openfeature/contrib/providers/ofrep/OfrepProvider.java
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,139 @@ | ||
package dev.openfeature.contrib.providers.ofrep; | ||
|
||
import dev.openfeature.contrib.providers.ofrep.internal.Resolver; | ||
import dev.openfeature.sdk.EvaluationContext; | ||
import dev.openfeature.sdk.FeatureProvider; | ||
import dev.openfeature.sdk.Metadata; | ||
import dev.openfeature.sdk.ProviderEvaluation; | ||
import dev.openfeature.sdk.Value; | ||
import java.util.concurrent.Executor; | ||
import java.util.concurrent.ExecutorService; | ||
import java.util.concurrent.TimeUnit; | ||
import lombok.extern.slf4j.Slf4j; | ||
import org.apache.commons.validator.routines.UrlValidator; | ||
|
||
/** | ||
* OpenFeature provider for OFREP. | ||
*/ | ||
@Slf4j | ||
public final class OfrepProvider implements FeatureProvider { | ||
Rahul-Baradol marked this conversation as resolved.
Show resolved
Hide resolved
|
||
|
||
private static final String OFREP_PROVIDER = "ofrep"; | ||
private static final long DEFAULT_EXECUTOR_SHUTDOWN_TIMEOUT = 1000; | ||
|
||
private final Resolver ofrepResolver; | ||
private Executor executor; | ||
|
||
public static OfrepProvider constructProvider() { | ||
return new OfrepProvider(); | ||
} | ||
|
||
/** | ||
* Constructs an OfrepProvider with the specified options. | ||
* | ||
* @param options The options for configuring the provider. | ||
* @return An instance of OfrepProvider configured with the provided options. | ||
* @throws IllegalArgumentException if any of the options are invalid. | ||
*/ | ||
public static OfrepProvider constructProvider(OfrepProviderOptions options) { | ||
if (!isValidUrl(options.getBaseUrl())) { | ||
throw new IllegalArgumentException("Invalid base URL: " + options.getBaseUrl()); | ||
} | ||
|
||
if (options.getHeaders() == null) { | ||
throw new IllegalArgumentException("Headers cannot be null"); | ||
} | ||
|
||
if (options.getRequestTimeout() == null | ||
|| options.getRequestTimeout().isNegative() | ||
|| options.getRequestTimeout().isZero()) { | ||
throw new IllegalArgumentException("Request timeout must be a positive duration"); | ||
} | ||
|
||
if (options.getConnectTimeout() == null | ||
|| options.getConnectTimeout().isNegative() | ||
|| options.getConnectTimeout().isZero()) { | ||
throw new IllegalArgumentException("Connect timeout must be a positive duration"); | ||
} | ||
|
||
if (options.getProxySelector() == null) { | ||
throw new IllegalArgumentException("ProxySelector cannot be null"); | ||
} | ||
|
||
if (options.getExecutor() == null) { | ||
throw new IllegalArgumentException("Executor cannot be null"); | ||
} | ||
|
||
return new OfrepProvider(options); | ||
} | ||
|
||
private OfrepProvider() { | ||
this(new OfrepProviderOptions.Builder().build()); | ||
} | ||
|
||
private OfrepProvider(OfrepProviderOptions options) { | ||
this.executor = options.getExecutor(); | ||
this.ofrepResolver = new Resolver( | ||
options.getBaseUrl(), | ||
options.getHeaders(), | ||
options.getRequestTimeout(), | ||
options.getConnectTimeout(), | ||
options.getProxySelector(), | ||
options.getExecutor()); | ||
} | ||
|
||
@Override | ||
public Metadata getMetadata() { | ||
return () -> OFREP_PROVIDER; | ||
} | ||
|
||
@Override | ||
public void shutdown() { | ||
if (executor instanceof ExecutorService) { | ||
ExecutorService executorService = (ExecutorService) executor; | ||
try { | ||
executorService.shutdown(); | ||
|
||
if (!executorService.awaitTermination(DEFAULT_EXECUTOR_SHUTDOWN_TIMEOUT, TimeUnit.MILLISECONDS)) { | ||
executorService.shutdownNow(); | ||
if (!executorService.awaitTermination(DEFAULT_EXECUTOR_SHUTDOWN_TIMEOUT, TimeUnit.MILLISECONDS)) { | ||
log.error("Provider couldn't shutdown gracefully."); | ||
} | ||
} | ||
} catch (InterruptedException e) { | ||
executorService.shutdownNow(); | ||
Thread.currentThread().interrupt(); | ||
} | ||
} | ||
} | ||
|
||
@Override | ||
public ProviderEvaluation<Boolean> getBooleanEvaluation(String key, Boolean defaultValue, EvaluationContext ctx) { | ||
return ofrepResolver.resolveBoolean(key, defaultValue, ctx); | ||
} | ||
|
||
@Override | ||
public ProviderEvaluation<String> getStringEvaluation(String key, String defaultValue, EvaluationContext ctx) { | ||
return ofrepResolver.resolveString(key, defaultValue, ctx); | ||
} | ||
|
||
@Override | ||
public ProviderEvaluation<Integer> getIntegerEvaluation(String key, Integer defaultValue, EvaluationContext ctx) { | ||
return ofrepResolver.resolveInteger(key, defaultValue, ctx); | ||
} | ||
|
||
@Override | ||
public ProviderEvaluation<Double> getDoubleEvaluation(String key, Double defaultValue, EvaluationContext ctx) { | ||
return ofrepResolver.resolveDouble(key, defaultValue, ctx); | ||
} | ||
|
||
@Override | ||
public ProviderEvaluation<Value> getObjectEvaluation(String key, Value defaultValue, EvaluationContext ctx) { | ||
return ofrepResolver.resolveObject(key, defaultValue, ctx); | ||
} | ||
|
||
private static boolean isValidUrl(String url) { | ||
UrlValidator validator = new UrlValidator(new String[] {"http", "https"}, UrlValidator.ALLOW_LOCAL_URLS); | ||
return validator.isValid(url); | ||
} | ||
} |
38 changes: 38 additions & 0 deletions
38
...ers/ofrep/src/main/java/dev/openfeature/contrib/providers/ofrep/OfrepProviderOptions.java
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,38 @@ | ||
package dev.openfeature.contrib.providers.ofrep; | ||
|
||
import com.google.common.collect.ImmutableList; | ||
import com.google.common.collect.ImmutableMap; | ||
import java.net.ProxySelector; | ||
import java.time.Duration; | ||
import java.util.concurrent.Executor; | ||
import java.util.concurrent.Executors; | ||
import lombok.Builder; | ||
import lombok.Getter; | ||
|
||
/** | ||
* Options for configuring the OFREP provider. | ||
*/ | ||
@Getter | ||
@Builder(builderClassName = "Builder", buildMethodName = "build") | ||
public class OfrepProviderOptions { | ||
|
||
private static final int DEFAULT_THREAD_POOL_SIZE = 5; | ||
|
||
@Builder.Default | ||
private final String baseUrl = "http://localhost:8016"; | ||
|
||
@Builder.Default | ||
private final ProxySelector proxySelector = ProxySelector.getDefault(); | ||
|
||
@Builder.Default | ||
private final Executor executor = Executors.newFixedThreadPool(DEFAULT_THREAD_POOL_SIZE); | ||
|
||
@Builder.Default | ||
private final Duration requestTimeout = Duration.ofSeconds(10); | ||
|
||
@Builder.Default | ||
private final Duration connectTimeout = Duration.ofSeconds(10); | ||
|
||
@Builder.Default | ||
private final ImmutableMap<String, ImmutableList<String>> headers = ImmutableMap.of(); | ||
} |
Oops, something went wrong.
Add this suggestion to a batch that can be applied as a single commit.
This suggestion is invalid because no changes were made to the code.
Suggestions cannot be applied while the pull request is closed.
Suggestions cannot be applied while viewing a subset of changes.
Only one suggestion per line can be applied in a batch.
Add this suggestion to a batch that can be applied as a single commit.
Applying suggestions on deleted lines is not supported.
You must change the existing code in this line in order to create a valid suggestion.
Outdated suggestions cannot be applied.
This suggestion has been applied or marked resolved.
Suggestions cannot be applied from pending reviews.
Suggestions cannot be applied on multi-line comments.
Suggestions cannot be applied while the pull request is queued to merge.
Suggestion cannot be applied right now. Please check back later.
Uh oh!
There was an error while loading. Please reload this page.