Skip to content

Commit fd84e65

Browse files
feat: flagsmith provider (#167)
* feat/flagsmith-provider: Add a flagsmith provider Signed-off-by: Andrew Helsby <[email protected]> Co-authored-by: Matthew Elwell <[email protected]>
1 parent 62a02fb commit fd84e65

File tree

16 files changed

+1519
-3
lines changed

16 files changed

+1519
-3
lines changed

.gitignore

+4-1
Original file line numberDiff line numberDiff line change
@@ -9,4 +9,7 @@ target
99
.DS_Store
1010

1111
# vscode stuff
12-
.vscode/
12+
.vscode/
13+
14+
# IntelliJ files
15+
*.iml

CODEOWNERS

+4-1
Original file line numberDiff line numberDiff line change
@@ -3,4 +3,7 @@
33
* @toddbaert @kavindu-dodan
44

55
# Go Feature Flag
6-
/providers/go-feature-flag/ @thomaspoignant
6+
/providers/go-feature-flag/ @thomaspoignant
7+
8+
# Flagsmith
9+
providers/flagsmith @ajhelsby

pom.xml

+1
Original file line numberDiff line numberDiff line change
@@ -29,6 +29,7 @@
2929
<modules>
3030
<module>hooks/open-telemetry</module>
3131
<module>providers/flagd</module>
32+
<module>providers/flagsmith</module>
3233
<module>providers/go-feature-flag</module>
3334
</modules>
3435

providers/flagsmith/README.md

+80
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,80 @@
1+
# Flagsmith OpenFeature Java Provider
2+
![Experimental](https://img.shields.io/badge/experimental-breaking%20changes%20allowed-yellow)
3+
[![Download](https://img.shields.io/maven-central/v/com.flagsmith/flagsmith-java-client)](https://mvnrepository.com/artifact/com.flagsmith/flagsmith-java-client)
4+
Flagsmith provides an all-in-one platform for developing, implementing, and managing your feature flags.
5+
6+
## Installation
7+
8+
<!-- x-release-please-start-version -->
9+
10+
```xml
11+
12+
<dependency>
13+
<groupId>dev.openfeature.contrib.providers</groupId>
14+
<artifactId>flagsmith</artifactId>
15+
<version>0.0.1</version>
16+
</dependency>
17+
```
18+
19+
<!-- x-release-please-end-version -->
20+
21+
## Usage
22+
23+
The `FlagsmithProvider` communicates with Flagsmith using the Flagsmith java sdk. Information on the sdk can be found in
24+
the Flagsmith documentaiton here https://docs.flagsmith.com/clients/server-side. The following code snippet shows how to
25+
initialize the `FlagsmithProvider`:
26+
27+
```java
28+
FlagsmithProviderOptions options = FlagsmithProviderOptions.builder()
29+
.apiKey("API_KEY")
30+
.build();
31+
32+
FlagsmithProvider provider = new FlagsmithProvider();
33+
OpenFeatureAPI.getInstance().setProvider(provider);
34+
```
35+
36+
Options can be defined using the FlagsmithProviderOptions builder. Below are all the options:
37+
38+
| Option name | Type | Default | Description
39+
| ----------- | ------- | --------- | ---------
40+
| apiKey | String | | Your API Token. Note that this is either the `Environment API` key or the `Server Side SDK Token`
41+
| headers | HashMap<String, String> | | Add custom headers which will be sent with each network request to the Flagsmith API.
42+
| envFlagsCacheKey | String | | Enable in-memory caching for the Flagsmith API.
43+
| expireCacheAfterWriteTimeUnit | TimeUnit | TimeUnit.MINUTES | The time unit used for cache expiry after write.
44+
| expireCacheAfterWrite | int | -1 | The integer time for cache expiry after write.
45+
| expireCacheAfterAccessTimeUnit | TimeUnit | TimeUnit.MINUTES | The time unit used for cache expiry after reading.
46+
| expireCacheAfterAccess | int | -1 | The integer time for cache expiry after reading.
47+
| maxCacheSize | int | -1 | The maximum size of the cache in MB.
48+
| recordCacheStats | boolean | false | Whether cache statistics should be recorded.
49+
| baseUri | String | https://edge.api.flagsmith.com/api/v1/ | Override the default Flagsmith API URL if you are self-hosting.
50+
| connectTimeout | int | 2000 | The network timeout in milliseconds.
51+
| writeTimeout | int | 5000 | The network timeout in milliseconds when writing.
52+
| readTimeout | int | 5000 | The network timeout in milliseconds when reading.
53+
| sslSocketFactory | SSLSocketFactory | | Override the sslSocketFactory.
54+
| trustManager | X509TrustManager | | X509TrustManager used when overriding the sslSocketFactory.
55+
| httpInterceptor | Interceptor | | Add a custom HTTP interceptor in the form of an okhttp3.Interceptor object.
56+
| retries | int | 3 | Add a custom com.flagsmith.config.Retry object to configure the backoff / retry configuration.
57+
| localEvaluation | boolean | false | Controls which mode to run in; local or remote evaluation.
58+
| environmentRefreshIntervalSeconds | int | 60 | Set environment refresh rate with polling manager.
59+
| enableAnalytics | boolean | false | Controls whether Flag Analytics data is sent to the Flagsmith API
60+
| usingBooleanConfigValue | boolean | false | Determines whether to resolve a feature value as a boolean or use the isFeatureEnabled as the flag itself. These values will be false and true respectively.
61+
62+
### Identity flags
63+
64+
In order to use specific identity flags, a targeting key must be provided in the EvaluationContext provided to the flag
65+
evaluation method. An example of this can be seen below:
66+
67+
```java
68+
FlagsmithProviderOptions options = FlagsmithProviderOptions.builder()
69+
.apiKey("API_KEY")
70+
.build();
71+
72+
FlagsmithProvider provider = new FlagsmithProvider();
73+
OpenFeatureAPI.getInstance().setProvider(provider);
74+
75+
EvaluationContext evaluationContext = new MutableContext();
76+
evaluationContext.setTargetingKey("my-identity");
77+
78+
Client client = api.getClient();
79+
boolean flag = client.getBooleanValue("key", false);
80+
```

providers/flagsmith/pom.xml

+52
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,52 @@
1+
<?xml version="1.0" encoding="UTF-8"?>
2+
<project xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xmlns="http://maven.apache.org/POM/4.0.0"
3+
xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 https://maven.apache.org/xsd/maven-4.0.0.xsd">
4+
<modelVersion>4.0.0</modelVersion>
5+
<parent>
6+
<groupId>dev.openfeature.contrib</groupId>
7+
<artifactId>parent</artifactId>
8+
<version>0.1.0</version>
9+
<relativePath>../../pom.xml</relativePath>
10+
</parent>
11+
<groupId>dev.openfeature.contrib.providers</groupId>
12+
<artifactId>flagsmith</artifactId>
13+
<version>0.0.1</version> <!--x-release-please-version -->
14+
15+
<name>flagsmith</name>
16+
<description>Flagsmith provider for Java</description>
17+
<url>https://flagsmith.com</url>
18+
19+
<developers>
20+
<developer>
21+
<id>ajhelsby</id>
22+
<name>Andrew Helsby</name>
23+
<organization>Flagsmith</organization>
24+
<url>https://flagsmith.com</url>
25+
</developer>
26+
</developers>
27+
28+
<dependencies>
29+
<!-- Inherits dev.openfeature.javasdk and the test dependencies from the parent pom -->
30+
<dependency>
31+
<groupId>com.flagsmith</groupId>
32+
<artifactId>flagsmith-java-client</artifactId>
33+
<version>5.1.2</version>
34+
</dependency>
35+
36+
<dependency>
37+
<groupId>com.squareup.okhttp3</groupId>
38+
<artifactId>okhttp</artifactId>
39+
<version>4.0.1</version>
40+
</dependency>
41+
42+
<!-- test -->
43+
<dependency>
44+
<groupId>com.squareup.okhttp3</groupId>
45+
<artifactId>mockwebserver</artifactId>
46+
<version>4.0.1</version>
47+
<scope>test</scope>
48+
</dependency>
49+
50+
</dependencies>
51+
52+
</project>
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,167 @@
1+
package dev.openfeature.contrib.providers.flagsmith;
2+
3+
import com.flagsmith.FlagsmithClient;
4+
import com.flagsmith.config.FlagsmithCacheConfig;
5+
import com.flagsmith.config.Retry;
6+
import dev.openfeature.contrib.providers.flagsmith.exceptions.InvalidCacheOptionsException;
7+
import dev.openfeature.contrib.providers.flagsmith.exceptions.InvalidOptionsException;
8+
9+
/**
10+
* FlagsmithClientConfigurer helps set up and validate the options for the FlagsmithClient
11+
* used by the FlagsmithProvider class.
12+
*/
13+
public class FlagsmithClientConfigurer {
14+
15+
/**
16+
* initializeProvider is initializing the different class element used by the provider.
17+
*
18+
* @param options the options used to create the provider
19+
*/
20+
static FlagsmithClient initializeProvider(FlagsmithProviderOptions options) {
21+
22+
validateOptions(options);
23+
24+
FlagsmithClient.Builder flagsmithBuilder = FlagsmithClient
25+
.newBuilder();
26+
// Set main configuration settings
27+
flagsmithBuilder.setApiKey(options.getApiKey());
28+
29+
if (options.getHeaders() != null && !options.getHeaders().isEmpty()) {
30+
flagsmithBuilder.withCustomHttpHeaders(options.getHeaders());
31+
}
32+
33+
if (options.getEnvFlagsCacheKey() != null) {
34+
FlagsmithCacheConfig flagsmithCacheConfig = initializeCacheConfig(options);
35+
flagsmithBuilder.withCache(flagsmithCacheConfig);
36+
}
37+
38+
com.flagsmith.config.FlagsmithConfig flagsmithConfig = initializeConfig(options);
39+
flagsmithBuilder.withConfiguration(flagsmithConfig);
40+
41+
return flagsmithBuilder.build();
42+
}
43+
44+
/**
45+
* Sets the cache related configuration for the provider using
46+
* the FlagsmithCacheConfig builder.
47+
*
48+
* @param options the options used to create the provider
49+
* @return a FlagsmithCacheConfig object containing the FlagsmithClient cache options
50+
*/
51+
private static FlagsmithCacheConfig initializeCacheConfig(FlagsmithProviderOptions options) {
52+
FlagsmithCacheConfig.Builder flagsmithCacheConfig = FlagsmithCacheConfig.newBuilder();
53+
54+
// Set cache configuration settings
55+
if (options.getEnvFlagsCacheKey() != null) {
56+
flagsmithCacheConfig.enableEnvLevelCaching(options.getEnvFlagsCacheKey());
57+
}
58+
59+
if (options.getExpireCacheAfterWrite() > -1
60+
&& options.getExpireCacheAfterWriteTimeUnit() != null) {
61+
flagsmithCacheConfig.expireAfterAccess(
62+
options.getExpireCacheAfterWrite(),
63+
options.getExpireCacheAfterWriteTimeUnit());
64+
}
65+
66+
if (options.getExpireCacheAfterAccess() > -1
67+
&& options.getExpireCacheAfterAccessTimeUnit() != null) {
68+
flagsmithCacheConfig.expireAfterAccess(
69+
options.getExpireCacheAfterAccess(),
70+
options.getExpireCacheAfterAccessTimeUnit());
71+
}
72+
73+
if (options.getMaxCacheSize() > -1) {
74+
flagsmithCacheConfig.maxSize(options.getMaxCacheSize());
75+
}
76+
77+
if (options.isRecordCacheStats()) {
78+
flagsmithCacheConfig.recordStats();
79+
}
80+
81+
return flagsmithCacheConfig.build();
82+
}
83+
84+
/**
85+
* Set the configuration options for the FlagsmithClient using
86+
* the FlagsmithConfig builder.
87+
*
88+
* @param options The options used to create the provider
89+
* @return a FlagsmithConfig object with the FlagsmithClient settings
90+
*/
91+
private static com.flagsmith.config.FlagsmithConfig initializeConfig(
92+
FlagsmithProviderOptions options) {
93+
com.flagsmith.config.FlagsmithConfig.Builder flagsmithConfig = com.flagsmith.config.FlagsmithConfig
94+
.newBuilder();
95+
96+
// Set client level configuration settings
97+
if (options.getBaseUri() != null) {
98+
flagsmithConfig.baseUri(options.getBaseUri());
99+
}
100+
101+
if (options.getConnectTimeout() > -1) {
102+
flagsmithConfig.connectTimeout(options.getConnectTimeout());
103+
}
104+
105+
if (options.getWriteTimeout() > -1) {
106+
flagsmithConfig.writeTimeout(options.getWriteTimeout());
107+
}
108+
109+
if (options.getReadTimeout() > -1) {
110+
flagsmithConfig.readTimeout(options.getReadTimeout());
111+
}
112+
113+
if (options.getSslSocketFactory() != null && options.getTrustManager() != null) {
114+
flagsmithConfig
115+
.sslSocketFactory(options.getSslSocketFactory(), options.getTrustManager());
116+
}
117+
118+
if (options.getHttpInterceptor() != null) {
119+
flagsmithConfig.addHttpInterceptor(options.getHttpInterceptor());
120+
}
121+
122+
if (options.getRetries() > -1) {
123+
flagsmithConfig.retries(new Retry(options.getRetries()));
124+
}
125+
126+
if (options.isLocalEvaluation()) {
127+
flagsmithConfig.withLocalEvaluation(options.isLocalEvaluation());
128+
}
129+
130+
if (options.getEnvironmentRefreshIntervalSeconds() > -1) {
131+
flagsmithConfig.withEnvironmentRefreshIntervalSeconds(options
132+
.getEnvironmentRefreshIntervalSeconds());
133+
}
134+
135+
if (options.isEnableAnalytics()) {
136+
flagsmithConfig.withEnableAnalytics(options.isEnableAnalytics());
137+
}
138+
139+
return flagsmithConfig.build();
140+
}
141+
142+
/**
143+
* Check the options that have been provided to see if there are any issues.
144+
* Exceptions will be thrown if there are issues found with the options.
145+
*
146+
* @param options the options used to create the provider
147+
*/
148+
private static void validateOptions(FlagsmithProviderOptions options) {
149+
if (options == null) {
150+
throw new InvalidOptionsException("No options provided");
151+
}
152+
153+
if (options.getApiKey() == null || options.getApiKey().isEmpty()) {
154+
throw new InvalidOptionsException("Flagsmith API key has not been set.");
155+
}
156+
157+
if (options.getEnvFlagsCacheKey() == null
158+
&& (options.getExpireCacheAfterWrite() > -1
159+
|| options.getExpireCacheAfterAccess() > -1
160+
|| options.getMaxCacheSize() > -1
161+
|| options.isRecordCacheStats())) {
162+
throw new InvalidCacheOptionsException(
163+
"No Flagsmith cache key provided but other cache settings have been set."
164+
);
165+
}
166+
}
167+
}

0 commit comments

Comments
 (0)