Skip to content
Merged
Show file tree
Hide file tree
Changes from 5 commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 2 additions & 0 deletions .github/component_owners.yml
Original file line number Diff line number Diff line change
Expand Up @@ -37,6 +37,8 @@ components:
- liran2000
providers/multiprovider:
- liran2000
providers/ofrep-provider:
- Rahul-Baradol
tools/flagd-http-connector:
- liran2000

Expand Down
1 change: 1 addition & 0 deletions .release-please-manifest.json
Original file line number Diff line number Diff line change
Expand Up @@ -10,6 +10,7 @@
"providers/configcat": "0.1.0",
"providers/statsig": "0.1.0",
"providers/multiprovider": "0.0.1",
"providers/ofrep": "0.0.1",
"tools/junit-openfeature": "0.1.2",
"tools/flagd-http-connector": "0.0.2",
".": "0.2.2"
Expand Down
1 change: 1 addition & 0 deletions pom.xml
Original file line number Diff line number Diff line change
Expand Up @@ -40,6 +40,7 @@
<module>providers/configcat</module>
<module>providers/statsig</module>
<module>providers/multiprovider</module>
<module>providers/ofrep</module>
<module>tools/flagd-http-connector</module>
</modules>

Expand Down
74 changes: 74 additions & 0 deletions providers/ofrep/README.md
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.

85 changes: 85 additions & 0 deletions providers/ofrep/pom.xml
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>0.2.2</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>
Original file line number Diff line number Diff line change
@@ -0,0 +1,125 @@
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 {

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.getTimeout() == null
|| options.getTimeout().isNegative()
|| options.getTimeout().isZero()) {
throw new IllegalArgumentException("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.getTimeout(),
options.getProxySelector(),
options.getExecutor());
}

@Override
public Metadata getMetadata() {
return () -> OFREP_PROVIDER;
}

@Override
public void shutdown() {
if (executor instanceof ExecutorService) {
try {
ExecutorService executorService = (ExecutorService) executor;
executorService.shutdownNow();
executorService.awaitTermination(DEFAULT_EXECUTOR_SHUTDOWN_TIMEOUT, TimeUnit.MILLISECONDS);
} catch (InterruptedException e) {
log.error("Error during shutdown {}", OFREP_PROVIDER, e);
}
}
}

@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);
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,35 @@
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 timeout = Duration.ofSeconds(10);

@Builder.Default
private final ImmutableMap<String, ImmutableList<String>> headers = ImmutableMap.of();
}
Loading