All Downloads are FREE. Search and download functionalities are using the official Maven repository.

com.launchdarkly.openfeature.serverprovider.Provider Maven / Gradle / Ivy

package com.launchdarkly.openfeature.serverprovider;

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 dev.openfeature.sdk.*;

import java.io.IOException;
import java.util.Collections;
import java.util.concurrent.CompletableFuture;
import java.util.concurrent.Future;

/**
 * An OpenFeature {@link FeatureProvider} which enables the use of the LaunchDarkly Server-Side SDK for Java
 * with OpenFeature.
 * 

 * import dev.openfeature.sdk.OpenFeatureAPI;
 *
 * public class Main {
 *  public static void main(String[] args) {
 *    OpenFeatureAPI.getInstance().setProvider(new Provider("fake-key"));
 *
 *    // Refer to OpenFeature documentation for getting a client and performing evaluations.
 *  }
 * }
 * 
*/ public class Provider extends EventProvider { private static final class ProviderMetaData implements Metadata { @Override public String getName() { return "LaunchDarkly.OpenFeature.ServerProvider"; } } private final Metadata metaData = new ProviderMetaData(); private final LDLogger logger; private final EvaluationDetailConverter evaluationDetailConverter; private final ValueConverter valueConverter; private final EvaluationContextConverter evaluationContextConverter; private final LDClientInterface client; private ProviderState state = ProviderState.NOT_READY; private final Object stateLock = new Object(); /** * Create a provider with the specified SDK and default configuration. *

* If you need to specify any configuration use {@link Provider#Provider(String, LDConfig)} instead. * * @param sdkKey the SDK key for your LaunchDarkly environment */ public Provider(String sdkKey) { this(sdkKey, new LDConfig.Builder().build()); } /** * Crate a provider with the specified SDK key and configuration. * * @param sdkKey the SDK key for your LaunchDarkly environment * @param config a client configuration object */ 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(); evaluationContextConverter = new EvaluationContextConverter(logger); evaluationDetailConverter = new EvaluationDetailConverter(logger); valueConverter = new ValueConverter(logger); } @Override public Metadata getMetadata() { return metaData; } @Override public ProviderEvaluation getBooleanEvaluation(String key, Boolean defaultValue, EvaluationContext ctx) { EvaluationDetail detail = this.client.boolVariationDetail(key, evaluationContextConverter.toLdContext(ctx), defaultValue); return evaluationDetailConverter.toEvaluationDetails(detail); } @Override public ProviderEvaluation getStringEvaluation(String key, String defaultValue, EvaluationContext ctx) { EvaluationDetail detail = this.client.stringVariationDetail(key, evaluationContextConverter.toLdContext(ctx), defaultValue); return evaluationDetailConverter.toEvaluationDetails(detail); } @Override public ProviderEvaluation getIntegerEvaluation(String key, Integer defaultValue, EvaluationContext ctx) { EvaluationDetail detail = this.client.intVariationDetail(key, evaluationContextConverter.toLdContext(ctx), defaultValue); return evaluationDetailConverter.toEvaluationDetails(detail); } @Override public ProviderEvaluation getDoubleEvaluation(String key, Double defaultValue, EvaluationContext ctx) { EvaluationDetail detail = this.client.doubleVariationDetail(key, evaluationContextConverter.toLdContext(ctx), defaultValue); return evaluationDetailConverter.toEvaluationDetails(detail); } @Override public ProviderEvaluation getObjectEvaluation(String key, Value defaultValue, EvaluationContext ctx) { EvaluationDetail detail = this.client.jsonValueVariationDetail( key, evaluationContextConverter.toLdContext(ctx), valueConverter.toLdValue(defaultValue)); return evaluationDetailConverter.toEvaluationDetailsLdValue(detail); } @Override public ProviderState getState() { synchronized (state) { 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; } var completer = new CompletableFuture(); client.getFlagTracker().addFlagChangeListener(detail -> { emitProviderConfigurationChanged( ProviderEventDetails.builder().flagsChanged(Collections.singletonList(detail.getKey())).build()); }); // Listen for future status changes. client.getDataSourceStatusProvider().addStatusListener((res) -> { handleDataSourceStatus(res, completer); }); if(state == ProviderState.READY) { return; } handleDataSourceStatus(client.getDataSourceStatusProvider().getStatus(), completer); var successfullyInitialized = completer.get(); if(!successfullyInitialized) { throw new RuntimeException("Failed to initialize LaunchDarkly client."); } } private void handleDataSourceStatus(DataSourceStatusProvider.Status res, CompletableFuture completer) { switch (res.getState()) { // We will not re-enter INITIALIZING, but it is here to make the switch exhaustive. case INITIALIZING: { } break; case INTERRUPTED: { setState(ProviderState.STALE); var message = res.getLastError() != null ? res.getLastError().getMessage() : "encountered an unknown error"; emitProviderStale(ProviderEventDetails.builder().message(message).build()); } break; case VALID: { boolean emit = false; synchronized (stateLock) { // 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) { emit = true; setState(ProviderState.READY); } } if (emit) { completer.complete(true); 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. setState(ProviderState.ERROR); completer.complete(false); emitProviderError(ProviderEventDetails.builder().message("Provider shutdown").build()); } } } private void setState(ProviderState state) { synchronized (stateLock) { this.state = state; } } @Override public void shutdown() { try { client.close(); } catch (IOException e) { throw new RuntimeException(e); } } /** * Get the LaunchDarkly client associated with this provider. *

* This can be used to access LaunchDarkly features which are not available in OpenFeature. * * @return the launchdarkly client instance */ public LDClientInterface getLdClient() { return client; } }





© 2015 - 2025 Weber Informatics LLC | Privacy Policy