-
Notifications
You must be signed in to change notification settings - Fork 1
feat: Update dependencies and spec support. #19
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
Changes from all commits
a605896
b10bf40
7d996df
3bd4249
48bf3e6
3be4f85
bef7895
e9a78e6
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
| Original file line number | Diff line number | Diff line change |
|---|---|---|
|
|
@@ -19,6 +19,8 @@ java { | |
| repositories { | ||
| mavenLocal() | ||
| mavenCentral() | ||
| // Before LaunchDarkly release artifacts get synced to Maven Central they are here along with snapshots: | ||
| maven { url "https://oss.sonatype.org/content/groups/public/" } | ||
|
Member
Author
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. To allow using pre-releases when we want to. |
||
| } | ||
|
|
||
| test { | ||
|
|
@@ -41,6 +43,15 @@ checkstyle { | |
| checkstyleTest.enabled = false | ||
| } | ||
|
|
||
| task generateJava(type: Copy) { | ||
|
Member
Author
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. The template code generation is so we have access to the version to put into the wrapper. |
||
| // This updates Version.java | ||
| from 'src/templates/java' | ||
| into "src/main/java" | ||
| filter(org.apache.tools.ant.filters.ReplaceTokens, tokens: [VERSION: version.toString()]) | ||
| } | ||
|
|
||
| compileJava.dependsOn 'generateJava' | ||
|
|
||
| publishing { | ||
| publications { | ||
| mavenJava(MavenPublication) { | ||
|
|
@@ -111,11 +122,19 @@ dependencies { | |
| // This dependency is used internally, and not exposed to consumers on their own compile classpath. | ||
| implementation 'com.google.guava:guava:23.0' | ||
|
|
||
| implementation group: 'com.launchdarkly', name: 'launchdarkly-java-server-sdk', version: '[6.0.0, 7.0.0)' | ||
| implementation 'dev.openfeature:sdk:[1.2.0,2.0.0)' | ||
| implementation group: 'com.launchdarkly', name: 'launchdarkly-java-server-sdk', version: '[7.1.0, 8.0.0)' | ||
|
|
||
| implementation 'dev.openfeature:sdk:[1.7.0,2.0.0)' | ||
|
|
||
| // Use JUnit test framework | ||
| testImplementation 'junit:junit:4.12' | ||
| testImplementation(platform('org.junit:junit-bom:5.10.0')) | ||
| testImplementation('org.junit.jupiter:junit-jupiter') | ||
|
Member
Author
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. I wanted some things from JUnit 5, so I moved forward. |
||
| testImplementation "org.mockito:mockito-core:3.+" | ||
| } | ||
|
|
||
| test { | ||
| useJUnitPlatform() | ||
| testLogging { | ||
| events "passed", "skipped", "failed" | ||
| } | ||
| } | ||
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -1,32 +1,36 @@ | ||
| package com.launchdarkly.openfeature.serverprovider; | ||
|
|
||
| import com.launchdarkly.logging.LDLogAdapter; | ||
| import com.launchdarkly.logging.LDLogger; | ||
| import com.launchdarkly.sdk.EvaluationDetail; | ||
| import com.launchdarkly.sdk.LDValue; | ||
| import com.launchdarkly.sdk.server.Components; | ||
| import com.launchdarkly.sdk.server.LDClient; | ||
| import com.launchdarkly.sdk.server.LDConfig; | ||
| import com.launchdarkly.sdk.server.interfaces.DataSourceStatusProvider; | ||
| import com.launchdarkly.sdk.server.interfaces.LDClientInterface; | ||
| import com.launchdarkly.sdk.server.subsystems.LoggingConfiguration; | ||
| import dev.openfeature.sdk.*; | ||
|
|
||
| import java.io.IOException; | ||
| import java.time.temporal.ChronoUnit; | ||
| import java.util.Collections; | ||
| import java.util.concurrent.TimeoutException; | ||
|
|
||
| /** | ||
| * An OpenFeature {@link FeatureProvider} which enables the use of the LaunchDarkly Server-Side SDK for Java | ||
| * with OpenFeature. | ||
| * <pre><code> | ||
| *import dev.openfeature.sdk.OpenFeatureAPI; | ||
| *import com.launchdarkly.sdk.server.LDClient; | ||
| * import dev.openfeature.sdk.OpenFeatureAPI; | ||
| * | ||
| *public class Main { | ||
| * public class Main { | ||
| * public static void main(String[] args) { | ||
| * LDClient ldClient = new LDClient("my-sdk-key"); | ||
| * OpenFeatureAPI.getInstance().setProvider(new Provider(ldClient)); | ||
| * OpenFeatureAPI.getInstance().setProvider(new Provider("fake-key")); | ||
| * | ||
| * // Refer to OpenFeature documentation for getting a client and performing evaluations. | ||
| * } | ||
| *} | ||
| * } | ||
| * </code></pre> | ||
| */ | ||
| public class Provider implements FeatureProvider { | ||
| public class Provider extends EventProvider { | ||
| private static final class ProviderMetaData implements Metadata { | ||
| @Override | ||
| public String getName() { | ||
|
|
@@ -43,39 +47,38 @@ public String getName() { | |
|
|
||
| private final LDClientInterface client; | ||
|
|
||
| private ProviderState state = ProviderState.NOT_READY; | ||
|
|
||
| /** | ||
| * Create a provider with the given LaunchDarkly client and provider configuration. | ||
| * <pre><code> | ||
| * // Using the provider with a custom log level. | ||
| * new Provider(ldclient, ProviderConfiguration | ||
| * .builder() | ||
| * .logging(Components.logging().level(LDLogLevel.INFO) | ||
| * .build()); | ||
| * </code></pre> | ||
| * Create a provider with the specified SDK and default configuration. | ||
| * <p> | ||
| * If you need to specify any configuration use {@link Provider#Provider(String, LDConfig)} instead. | ||
| * | ||
| * @param client A {@link LDClient} instance. | ||
| * @param config Configuration for the provider. | ||
| * @param sdkKey the SDK key for your LaunchDarkly environment | ||
| */ | ||
| public Provider(LDClientInterface client, ProviderConfiguration config) { | ||
| this.client = client; | ||
| LoggingConfiguration loggingConfig = config.getLoggingConfigurationFactory().build(null); | ||
| LDLogAdapter adapter = loggingConfig.getLogAdapter(); | ||
| logger = LDLogger.withAdapter(adapter, loggingConfig.getBaseLoggerName()); | ||
|
|
||
| evaluationContextConverter = new EvaluationContextConverter(logger); | ||
| evaluationDetailConverter = new EvaluationDetailConverter(logger); | ||
| valueConverter = new ValueConverter(logger); | ||
| public Provider(String sdkKey) { | ||
| this(sdkKey, new LDConfig.Builder().build()); | ||
| } | ||
|
|
||
| /** | ||
| * Create a provider with the given LaunchDarkly client. | ||
| * <p> | ||
| * The provider will be created with default configuration. | ||
| * Crate a provider with the specified SDK key and configuration. | ||
| * | ||
| * @param client A {@link LDClient} instance. | ||
| * @param sdkKey the SDK key for your LaunchDarkly environment | ||
| * @param config a client configuration object | ||
| */ | ||
| public Provider(LDClientInterface client) { | ||
| this(client, ProviderConfiguration.builder().build()); | ||
| public Provider(String sdkKey, LDConfig config) { | ||
| this(new LDClient(sdkKey, LDConfig.Builder.fromConfig(config) | ||
| .wrapper(Components.wrapperInfo() | ||
| .wrapperName("open-feature-java-server") | ||
| .wrapperVersion(Version.SDK_VERSION)).build())); | ||
| } | ||
|
|
||
| Provider(LDClientInterface client) { | ||
| this.client = client; | ||
| logger = client.getLogger(); | ||
|
Member
Author
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. We added this in the migrations changes, which is really handy for OpenFeature. |
||
| evaluationContextConverter = new EvaluationContextConverter(logger); | ||
| evaluationDetailConverter = new EvaluationDetailConverter(logger); | ||
| valueConverter = new ValueConverter(logger); | ||
| } | ||
|
|
||
| @Override | ||
|
|
@@ -86,41 +89,121 @@ public Metadata getMetadata() { | |
| @Override | ||
| public ProviderEvaluation<Boolean> getBooleanEvaluation(String key, Boolean defaultValue, EvaluationContext ctx) { | ||
| EvaluationDetail<Boolean> detail | ||
| = this.client.boolVariationDetail(key, evaluationContextConverter.toLdContext(ctx), defaultValue); | ||
| = this.client.boolVariationDetail(key, evaluationContextConverter.toLdContext(ctx), defaultValue); | ||
|
Member
Author
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. I feel like formatting always changes even when I am using the same rules and the same tools, but at different points in time.
Contributor
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. IDEs are weird. |
||
|
|
||
| return evaluationDetailConverter.toEvaluationDetails(detail); | ||
| } | ||
|
|
||
| @Override | ||
| public ProviderEvaluation<String> getStringEvaluation(String key, String defaultValue, EvaluationContext ctx) { | ||
| EvaluationDetail<String> detail | ||
| = this.client.stringVariationDetail(key, evaluationContextConverter.toLdContext(ctx), defaultValue); | ||
| = this.client.stringVariationDetail(key, evaluationContextConverter.toLdContext(ctx), defaultValue); | ||
|
|
||
| return evaluationDetailConverter.toEvaluationDetails(detail); | ||
| } | ||
|
|
||
| @Override | ||
| public ProviderEvaluation<Integer> getIntegerEvaluation(String key, Integer defaultValue, EvaluationContext ctx) { | ||
| EvaluationDetail<Integer> detail | ||
| = this.client.intVariationDetail(key, evaluationContextConverter.toLdContext(ctx), defaultValue); | ||
| = this.client.intVariationDetail(key, evaluationContextConverter.toLdContext(ctx), defaultValue); | ||
|
|
||
| return evaluationDetailConverter.toEvaluationDetails(detail); | ||
| } | ||
|
|
||
| @Override | ||
| public ProviderEvaluation<Double> getDoubleEvaluation(String key, Double defaultValue, EvaluationContext ctx) { | ||
| EvaluationDetail<Double> detail | ||
| = this.client.doubleVariationDetail(key, evaluationContextConverter.toLdContext(ctx), defaultValue); | ||
| = this.client.doubleVariationDetail(key, evaluationContextConverter.toLdContext(ctx), defaultValue); | ||
|
|
||
| return evaluationDetailConverter.toEvaluationDetails(detail); | ||
| } | ||
|
|
||
| @Override | ||
| public ProviderEvaluation<Value> getObjectEvaluation(String key, Value defaultValue, EvaluationContext ctx) { | ||
| EvaluationDetail<LDValue> detail | ||
| = this.client.jsonValueVariationDetail( | ||
| key, evaluationContextConverter.toLdContext(ctx), valueConverter.toLdValue(defaultValue)); | ||
| = this.client.jsonValueVariationDetail( | ||
| key, evaluationContextConverter.toLdContext(ctx), valueConverter.toLdValue(defaultValue)); | ||
|
|
||
| return evaluationDetailConverter.toEvaluationDetailsLdValue(detail); | ||
| } | ||
|
|
||
| @Override | ||
| public ProviderState getState() { | ||
| return state; | ||
| } | ||
|
|
||
| @Override | ||
| public void initialize(EvaluationContext evaluationContext) throws Exception { | ||
| // If we are ready, then set the state. Don't return, because we still need to listen for future | ||
| // changes. | ||
| if (client.isInitialized()) { | ||
| state = ProviderState.READY; | ||
| } | ||
|
|
||
| client.getFlagTracker().addFlagChangeListener(detail -> { | ||
| emitProviderConfigurationChanged( | ||
| ProviderEventDetails.builder().flagsChanged(Collections.singletonList(detail.getKey())).build()); | ||
| }); | ||
| // Listen for future status changes. | ||
| client.getDataSourceStatusProvider().addStatusListener((res) -> { | ||
| switch (res.getState()) { | ||
| // We will not re-enter INITIALIZING, but it is here to make the switch exhaustive. | ||
| case INITIALIZING: { | ||
| } | ||
| break; | ||
| case INTERRUPTED: { | ||
| state = ProviderState.STALE; | ||
| var message = res.getLastError() != null ? res.getLastError().getMessage() : "encountered an unknown error"; | ||
| emitProviderStale(ProviderEventDetails.builder().message(message).build()); | ||
| } | ||
| break; | ||
| case VALID: { | ||
| // If we are ready, then we don't want to emit it again. Other conditions we may be updating the | ||
| // reason we are stale or interrupted, so we want to emit an event each time. | ||
| if (state != ProviderState.READY) { | ||
| state = ProviderState.READY; | ||
| emitProviderReady(ProviderEventDetails.builder().build()); | ||
| } | ||
| } | ||
| break; | ||
| case OFF: { | ||
| // Currently there is not a shutdown state. | ||
| // Our client/provider cannot be restarted, so we just go to error. | ||
| state = ProviderState.ERROR; | ||
| emitProviderError(ProviderEventDetails.builder().message("Provider shutdown").build()); | ||
| } | ||
| } | ||
| }); | ||
| if (state == ProviderState.READY) { | ||
| return; | ||
| } | ||
|
|
||
| boolean initialized = client.getDataSourceStatusProvider().waitFor(DataSourceStatusProvider.State.VALID, | ||
| ChronoUnit.FOREVER.getDuration()); | ||
|
Member
Author
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Fun fact, FOREVER, is just the most digits that can put in without an overflow.
Member
Author
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. waitFor also should return if we hit the "OFF" state from a terminal error. |
||
|
|
||
| if (!initialized) { | ||
| // Here we throw an exception for the OpenFeature SDK, which will handle emitting an event. | ||
| throw new RuntimeException("Failed to initialize LaunchDarkly client."); | ||
| } | ||
| } | ||
|
|
||
| @Override | ||
| public void shutdown() { | ||
| try { | ||
| client.close(); | ||
| } catch (IOException e) { | ||
| throw new RuntimeException(e); | ||
| } | ||
| } | ||
|
|
||
| /** | ||
| * Get the LaunchDarkly client associated with this provider. | ||
| * <p> | ||
| * This can be used to access LaunchDarkly features which are not available in OpenFeature. | ||
| * | ||
| * @return the launchdarkly client instance | ||
| */ | ||
| public LDClientInterface getLdClient() { | ||
| return client; | ||
| } | ||
| } | ||
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Dropping 1.8 as it isn't supported by the OF Sdk.