diff --git a/src/main/java/com/launchdarkly/client/Components.java b/src/main/java/com/launchdarkly/client/Components.java
index e673b8f20..a700a0810 100644
--- a/src/main/java/com/launchdarkly/client/Components.java
+++ b/src/main/java/com/launchdarkly/client/Components.java
@@ -1,12 +1,15 @@
package com.launchdarkly.client;
import com.launchdarkly.client.integrations.PersistentDataStoreBuilder;
+import com.launchdarkly.client.integrations.PollingDataSourceBuilder;
+import com.launchdarkly.client.integrations.StreamingDataSourceBuilder;
import com.launchdarkly.client.interfaces.PersistentDataStoreFactory;
-import org.slf4j.Logger;
-import org.slf4j.LoggerFactory;
-
+import java.io.IOException;
import java.net.URI;
+import java.util.concurrent.Future;
+
+import static com.google.common.util.concurrent.Futures.immediateFuture;
/**
* Provides factories for the standard implementations of LaunchDarkly component interfaces.
@@ -19,22 +22,27 @@ public abstract class Components {
private static final UpdateProcessorFactory defaultUpdateProcessorFactory = new DefaultUpdateProcessorFactory();
private static final UpdateProcessorFactory nullUpdateProcessorFactory = new NullUpdateProcessorFactory();
+ private Components() {}
+
/**
- * Returns a factory for the default in-memory implementation of a data store.
+ * Returns a configuration object for using the default in-memory implementation of a data store.
+ *
+ * Since it is the default, you do not normally need to call this method, unless you need to create
+ * a data store instance for testing purposes.
*
* Note that the interface is still named {@link FeatureStoreFactory}, but in a future version it
* will be renamed to {@code DataStoreFactory}.
*
* @return a factory object
* @see LDConfig.Builder#dataStore(FeatureStoreFactory)
- * @since 4.11.0
+ * @since 4.12.0
*/
public static FeatureStoreFactory inMemoryDataStore() {
return inMemoryFeatureStoreFactory;
}
/**
- * Returns a configurable factory for some implementation of a persistent data store.
+ * Returns a configuration builder for some implementation of a persistent data store.
*
* This method is used in conjunction with another factory object provided by specific components
* such as the Redis integration. The latter provides builder methods for options that are specific
@@ -57,7 +65,7 @@ public static FeatureStoreFactory inMemoryDataStore() {
* @return a {@link PersistentDataStoreBuilder}
* @see LDConfig.Builder#dataStore(FeatureStoreFactory)
* @see com.launchdarkly.client.integrations.Redis
- * @since 4.11.0
+ * @since 4.12.0
*/
public static PersistentDataStoreBuilder persistentDataStore(PersistentDataStoreFactory storeFactory) {
return new PersistentDataStoreBuilder(storeFactory);
@@ -117,27 +125,80 @@ public static EventProcessorFactory defaultEventProcessor() {
public static EventProcessorFactory nullEventProcessor() {
return nullEventProcessorFactory;
}
+
+ /**
+ * Returns a configurable factory for using streaming mode to get feature flag data.
+ *
+ * By default, the SDK uses a streaming connection to receive feature flag data from LaunchDarkly. To use the
+ * default behavior, you do not need to call this method. However, if you want to customize the behavior of
+ * the connection, call this method to obtain a builder, change its properties with the
+ * {@link StreamingDataSourceBuilder} methods, and pass it to {@link LDConfig.Builder#dataSource(UpdateProcessorFactory)}:
+ *
+ * LDConfig config = new LDConfig.Builder()
+ * .dataSource(Components.streamingDataSource().initialReconnectDelayMillis(500))
+ * .build();
+ *
+ *
+ * These properties will override any equivalent deprecated properties that were set with {@code LDConfig.Builder},
+ * such as {@link LDConfig.Builder#reconnectTimeMs(long)}.
+ *
+ * (Note that the interface is still named {@link UpdateProcessorFactory}, but in a future version it
+ * will be renamed to {@code DataSourceFactory}.)
+ *
+ * @return a builder for setting streaming connection properties
+ * @since 4.12.0
+ */
+ public static StreamingDataSourceBuilder streamingDataSource() {
+ return new StreamingDataSourceBuilderImpl();
+ }
/**
- * Returns a factory for the default implementation of the component for receiving feature flag data
- * from LaunchDarkly. Based on your configuration, this implementation uses either streaming or
- * polling, or does nothing if the client is offline, or in LDD mode.
+ * Returns a configurable factory for using polling mode to get feature flag data.
+ *
+ * This is not the default behavior; by default, the SDK uses a streaming connection to receive feature flag
+ * data from LaunchDarkly. In polling mode, the SDK instead makes a new HTTP request to LaunchDarkly at regular
+ * intervals. HTTP caching allows it to avoid redundantly downloading data if there have been no changes, but
+ * polling is still less efficient than streaming and should only be used on the advice of LaunchDarkly support.
+ *
+ * To use polling mode, call this method to obtain a builder, change its properties with the
+ * {@link PollingDataSourceBuilder} methods, and pass it to {@link LDConfig.Builder#dataSource(UpdateProcessorFactory)}:
+ *
+ * LDConfig config = new LDConfig.Builder()
+ * .dataSource(Components.pollingDataSource().pollIntervalMillis(45000))
+ * .build();
+ *
+ *
+ * These properties will override any equivalent deprecated properties that were set with {@code LDConfig.Builder},
+ * such as {@link LDConfig.Builder#pollingIntervalMillis(long)}. However, setting {@link LDConfig.Builder#offline(boolean)}
+ * to {@code true} will supersede this setting and completely disable network requests.
+ *
+ * (Note that the interface is still named {@link UpdateProcessorFactory}, but in a future version it
+ * will be renamed to {@code DataSourceFactory}.)
*
- * Note that the interface is still named {@link UpdateProcessorFactory}, but in a future version it
- * will be renamed to {@code DataSourceFactory}.
- *
- * @return a factory object
- * @since 4.11.0
- * @see LDConfig.Builder#dataSource(UpdateProcessorFactory)
+ * @return a builder for setting polling properties
+ * @since 4.12.0
*/
- public static UpdateProcessorFactory defaultDataSource() {
- return defaultUpdateProcessorFactory;
+ public static PollingDataSourceBuilder pollingDataSource() {
+ return new PollingDataSourceBuilderImpl();
}
-
+
/**
- * Deprecated name for {@link #defaultDataSource()}.
+ * Deprecated method for using the default data source implementation.
+ *
+ * If you pass the return value of this method to {@link LDConfig.Builder#dataSource(UpdateProcessorFactory)},
+ * the behavior is as follows:
+ *
+ * - If you have set {@link LDConfig.Builder#offline(boolean)} or {@link LDConfig.Builder#useLdd(boolean)}
+ * to {@code true}, the SDK will not connect to LaunchDarkly for feature flag data.
+ *
- If you have set {@link LDConfig.Builder#stream(boolean)} to {@code false}, it will use polling mode--
+ * equivalent to using {@link #pollingDataSource()} with the options set by {@link LDConfig.Builder#baseURI(URI)}
+ * and {@link LDConfig.Builder#pollingIntervalMillis(long)}.
+ *
- Otherwise, it will use streaming mode-- equivalent to using {@link #streamingDataSource()} with
+ * the options set by {@link LDConfig.Builder#streamURI(URI)} and {@link LDConfig.Builder#reconnectTimeMs(long)}.
+ *
+ *
* @return a factory object
- * @deprecated Use {@link #defaultDataSource()}.
+ * @deprecated Use {@link #streamingDataSource()}, {@link #pollingDataSource()}, or {@link #externalUpdatesOnly()}.
*/
@Deprecated
public static UpdateProcessorFactory defaultUpdateProcessor() {
@@ -145,24 +206,38 @@ public static UpdateProcessorFactory defaultUpdateProcessor() {
}
/**
- * Returns a factory for a null implementation of {@link UpdateProcessor}, which does not
- * connect to LaunchDarkly, regardless of any other configuration.
- *
- * Note that the interface is still named {@link UpdateProcessorFactory}, but in a future version it
- * will be renamed to {@code DataSourceFactory}.
+ * Returns a configuration object that disables a direct connection with LaunchDarkly for feature flag updates.
+ *
+ * Passing this to {@link LDConfig.Builder#dataSource(UpdateProcessorFactory)} causes the SDK
+ * not to retrieve feature flag data from LaunchDarkly, regardless of any other configuration.
+ * This is normally done if you are using the Relay Proxy
+ * in "daemon mode", where an external process-- the Relay Proxy-- connects to LaunchDarkly and populates
+ * a persistent data store with the feature flag data. The data store could also be populated by
+ * another process that is running the LaunchDarkly SDK. If there is no external process updating
+ * the data store, then the SDK will not have any feature flag data and will return application
+ * default values only.
+ *
+ * LDConfig config = new LDConfig.Builder()
+ * .dataSource(Components.externalUpdatesOnly())
+ * .dataStore(Components.persistentDataStore(Redis.dataStore())) // assuming the Relay Proxy is using Redis
+ * .build();
+ *
+ *
+ * (Note that the interface is still named {@link UpdateProcessorFactory}, but in a future version it
+ * will be renamed to {@code DataSourceFactory}.)
*
* @return a factory object
- * @since 4.11.0
+ * @since 4.12.0
* @see LDConfig.Builder#dataSource(UpdateProcessorFactory)
*/
- public static UpdateProcessorFactory nullDataSource() {
+ public static UpdateProcessorFactory externalUpdatesOnly() {
return nullUpdateProcessorFactory;
}
/**
- * Deprecated name for {@link #nullDataSource()}.
+ * Deprecated name for {@link #externalUpdatesOnly()}.
* @return a factory object
- * @deprecated Use {@link #nullDataSource()}.
+ * @deprecated Use {@link #externalUpdatesOnly()}.
*/
@Deprecated
public static UpdateProcessorFactory nullUpdateProcessor() {
@@ -194,37 +269,117 @@ public EventProcessor createEventProcessor(String sdkKey, LDConfig config) {
}
private static final class DefaultUpdateProcessorFactory implements UpdateProcessorFactory {
- // Note, logger uses LDClient class name for backward compatibility
- private static final Logger logger = LoggerFactory.getLogger(LDClient.class);
-
- @SuppressWarnings("deprecation")
@Override
public UpdateProcessor createUpdateProcessor(String sdkKey, LDConfig config, FeatureStore featureStore) {
- if (config.offline) {
- logger.info("Starting LaunchDarkly client in offline mode");
- return new UpdateProcessor.NullUpdateProcessor();
- } else if (config.useLdd) {
- logger.info("Starting LaunchDarkly in LDD mode. Skipping direct feature retrieval.");
- return new UpdateProcessor.NullUpdateProcessor();
+ // We don't need to check config.offline or config.useLdd here; the former is checked automatically
+ // by StreamingDataSourceBuilder and PollingDataSourceBuilder, and setting the latter is translated
+ // into using externalUpdatesOnly() by LDConfig.Builder.
+ if (config.stream) {
+ return streamingDataSource()
+ .baseUri(config.deprecatedStreamURI)
+ .pollingBaseUri(config.deprecatedBaseURI)
+ .initialReconnectDelayMillis(config.deprecatedReconnectTimeMs)
+ .createUpdateProcessor(sdkKey, config, featureStore);
} else {
- DefaultFeatureRequestor requestor = new DefaultFeatureRequestor(sdkKey, config);
- if (config.stream) {
- logger.info("Enabling streaming API");
- return new StreamProcessor(sdkKey, config, requestor, featureStore, null);
- } else {
- logger.info("Disabling streaming API");
- logger.warn("You should only disable the streaming API if instructed to do so by LaunchDarkly support");
- return new PollingProcessor(config, requestor, featureStore);
- }
+ return pollingDataSource()
+ .baseUri(config.deprecatedBaseURI)
+ .pollIntervalMillis(config.deprecatedPollingIntervalMillis)
+ .createUpdateProcessor(sdkKey, config, featureStore);
}
}
}
private static final class NullUpdateProcessorFactory implements UpdateProcessorFactory {
- @SuppressWarnings("deprecation")
@Override
public UpdateProcessor createUpdateProcessor(String sdkKey, LDConfig config, FeatureStore featureStore) {
- return new UpdateProcessor.NullUpdateProcessor();
+ if (config.offline) {
+ // If they have explicitly called offline(true) to disable everything, we'll log this slightly
+ // more specific message.
+ LDClient.logger.info("Starting LaunchDarkly client in offline mode");
+ } else {
+ LDClient.logger.info("LaunchDarkly client will not connect to Launchdarkly for feature flag data");
+ }
+ return new NullUpdateProcessor();
+ }
+ }
+
+ // Package-private for visibility in tests
+ static final class NullUpdateProcessor implements UpdateProcessor {
+ @Override
+ public Future start() {
+ return immediateFuture(null);
+ }
+
+ @Override
+ public boolean initialized() {
+ return true;
+ }
+
+ @Override
+ public void close() throws IOException {}
+ }
+
+ private static final class StreamingDataSourceBuilderImpl extends StreamingDataSourceBuilder {
+ @Override
+ public UpdateProcessor createUpdateProcessor(String sdkKey, LDConfig config, FeatureStore featureStore) {
+ // Note, we log startup messages under the LDClient class to keep logs more readable
+
+ if (config.offline) {
+ LDClient.logger.info("Starting LaunchDarkly client in offline mode");
+ return Components.externalUpdatesOnly().createUpdateProcessor(sdkKey, config, featureStore);
+ }
+
+ LDClient.logger.info("Enabling streaming API");
+
+ URI streamUri = baseUri == null ? LDConfig.DEFAULT_STREAM_URI : baseUri;
+ URI pollUri;
+ if (pollingBaseUri != null) {
+ pollUri = pollingBaseUri;
+ } else {
+ // If they have set a custom base URI, and they did *not* set a custom polling URI, then we can
+ // assume they're using Relay in which case both of those values are the same.
+ pollUri = baseUri == null ? LDConfig.DEFAULT_BASE_URI : baseUri;
+ }
+
+ DefaultFeatureRequestor requestor = new DefaultFeatureRequestor(
+ sdkKey,
+ config,
+ pollUri,
+ false
+ );
+
+ return new StreamProcessor(
+ sdkKey,
+ config,
+ requestor,
+ featureStore,
+ null,
+ streamUri,
+ initialReconnectDelayMillis
+ );
+ }
+ }
+
+ private static final class PollingDataSourceBuilderImpl extends PollingDataSourceBuilder {
+ @Override
+ public UpdateProcessor createUpdateProcessor(String sdkKey, LDConfig config, FeatureStore featureStore) {
+ // Note, we log startup messages under the LDClient class to keep logs more readable
+
+ if (config.offline) {
+ LDClient.logger.info("Starting LaunchDarkly client in offline mode");
+ return Components.externalUpdatesOnly().createUpdateProcessor(sdkKey, config, featureStore);
+ }
+
+ LDClient.logger.info("Disabling streaming API");
+ LDClient.logger.warn("You should only disable the streaming API if instructed to do so by LaunchDarkly support");
+
+ DefaultFeatureRequestor requestor = new DefaultFeatureRequestor(
+ sdkKey,
+ config,
+ baseUri == null ? LDConfig.DEFAULT_BASE_URI : baseUri,
+ true
+ );
+ return new PollingProcessor(requestor, featureStore, pollIntervalMillis);
}
}
}
diff --git a/src/main/java/com/launchdarkly/client/DefaultFeatureRequestor.java b/src/main/java/com/launchdarkly/client/DefaultFeatureRequestor.java
index 99bbd9535..b0bfdc02a 100644
--- a/src/main/java/com/launchdarkly/client/DefaultFeatureRequestor.java
+++ b/src/main/java/com/launchdarkly/client/DefaultFeatureRequestor.java
@@ -7,6 +7,7 @@
import java.io.File;
import java.io.IOException;
+import java.net.URI;
import java.util.HashMap;
import java.util.Map;
@@ -21,7 +22,10 @@
import okhttp3.Request;
import okhttp3.Response;
-class DefaultFeatureRequestor implements FeatureRequestor {
+/**
+ * Implementation of getting flag data via a polling request. Used by both streaming and polling components.
+ */
+final class DefaultFeatureRequestor implements FeatureRequestor {
private static final Logger logger = LoggerFactory.getLogger(DefaultFeatureRequestor.class);
private static final String GET_LATEST_FLAGS_PATH = "/sdk/latest-flags";
private static final String GET_LATEST_SEGMENTS_PATH = "/sdk/latest-segments";
@@ -30,18 +34,22 @@ class DefaultFeatureRequestor implements FeatureRequestor {
private final String sdkKey;
private final LDConfig config;
+ private final URI baseUri;
private final OkHttpClient httpClient;
+ private final boolean useCache;
- DefaultFeatureRequestor(String sdkKey, LDConfig config) {
+ DefaultFeatureRequestor(String sdkKey, LDConfig config, URI baseUri, boolean useCache) {
this.sdkKey = sdkKey;
- this.config = config;
+ this.config = config; // this is no longer the source of truth for baseURI, but it can still affect HTTP behavior
+ this.baseUri = baseUri;
+ this.useCache = useCache;
OkHttpClient.Builder httpBuilder = new OkHttpClient.Builder();
configureHttpClientBuilder(config, httpBuilder);
// HTTP caching is used only for FeatureRequestor. However, when streaming is enabled, HTTP GETs
// made by FeatureRequester will always guarantee a new flag state, so we disable the cache.
- if (!config.stream) {
+ if (useCache) {
File cacheDir = Files.createTempDir();
Cache cache = new Cache(cacheDir, MAX_HTTP_CACHE_SIZE_BYTES);
httpBuilder.cache(cache);
@@ -78,7 +86,7 @@ public AllData getAllData() throws IOException, HttpErrorException {
private String get(String path) throws IOException, HttpErrorException {
Request request = getRequestBuilder(sdkKey)
- .url(config.baseURI.resolve(path).toURL())
+ .url(baseUri.resolve(path).toURL())
.get()
.build();
@@ -92,7 +100,7 @@ private String get(String path) throws IOException, HttpErrorException {
}
logger.debug("Get flag(s) response: " + response.toString() + " with body: " + body);
logger.debug("Network response: " + response.networkResponse());
- if(!config.stream) {
+ if (useCache) {
logger.debug("Cache hit count: " + httpClient.cache().hitCount() + " Cache network Count: " + httpClient.cache().networkCount());
logger.debug("Cache response: " + response.cacheResponse());
}
diff --git a/src/main/java/com/launchdarkly/client/EventOutputFormatter.java b/src/main/java/com/launchdarkly/client/EventOutputFormatter.java
index e1db41097..a8428ca69 100644
--- a/src/main/java/com/launchdarkly/client/EventOutputFormatter.java
+++ b/src/main/java/com/launchdarkly/client/EventOutputFormatter.java
@@ -13,7 +13,7 @@
* Rather than creating intermediate objects to represent this schema, we use the Gson streaming
* output API to construct JSON directly.
*/
-class EventOutputFormatter {
+final class EventOutputFormatter {
private final LDConfig config;
EventOutputFormatter(LDConfig config) {
diff --git a/src/main/java/com/launchdarkly/client/FeatureStoreCacheConfig.java b/src/main/java/com/launchdarkly/client/FeatureStoreCacheConfig.java
index 010886791..4c73d2f53 100644
--- a/src/main/java/com/launchdarkly/client/FeatureStoreCacheConfig.java
+++ b/src/main/java/com/launchdarkly/client/FeatureStoreCacheConfig.java
@@ -96,7 +96,7 @@ public enum StaleValuesPolicy {
/**
* Used internally for backward compatibility.
* @return the equivalent enum value
- * @since 4.11.0
+ * @since 4.12.0
*/
public PersistentDataStoreBuilder.StaleValuesPolicy toNewEnum() {
switch (this) {
@@ -113,7 +113,7 @@ public PersistentDataStoreBuilder.StaleValuesPolicy toNewEnum() {
* Used internally for backward compatibility.
* @param policy the enum value in the new API
* @return the equivalent enum value
- * @since 4.11.0
+ * @since 4.12.0
*/
public static StaleValuesPolicy fromNewEnum(PersistentDataStoreBuilder.StaleValuesPolicy policy) {
switch (policy) {
diff --git a/src/main/java/com/launchdarkly/client/HttpErrorException.java b/src/main/java/com/launchdarkly/client/HttpErrorException.java
index 8f6672bbb..8450e260f 100644
--- a/src/main/java/com/launchdarkly/client/HttpErrorException.java
+++ b/src/main/java/com/launchdarkly/client/HttpErrorException.java
@@ -1,7 +1,7 @@
package com.launchdarkly.client;
@SuppressWarnings("serial")
-class HttpErrorException extends Exception {
+final class HttpErrorException extends Exception {
private final int status;
public HttpErrorException(int status) {
diff --git a/src/main/java/com/launchdarkly/client/LDClient.java b/src/main/java/com/launchdarkly/client/LDClient.java
index 676c518ed..8e713fd0f 100644
--- a/src/main/java/com/launchdarkly/client/LDClient.java
+++ b/src/main/java/com/launchdarkly/client/LDClient.java
@@ -1,6 +1,7 @@
package com.launchdarkly.client;
import com.google.gson.JsonElement;
+import com.launchdarkly.client.Components.NullUpdateProcessor;
import com.launchdarkly.client.value.LDValue;
import org.apache.commons.codec.binary.Hex;
@@ -30,7 +31,9 @@
* a single {@code LDClient} for the lifetime of their application.
*/
public final class LDClient implements LDClientInterface {
- private static final Logger logger = LoggerFactory.getLogger(LDClient.class);
+ // Package-private so other classes can log under the top-level logger's tag
+ static final Logger logger = LoggerFactory.getLogger(LDClient.class);
+
private static final String HMAC_ALGORITHM = "HmacSHA256";
static final String CLIENT_VERSION = getClientVersion();
@@ -83,12 +86,13 @@ public LDClient(String sdkKey, LDConfig config) {
Components.defaultEventProcessor() : config.eventProcessorFactory;
this.eventProcessor = epFactory.createEventProcessor(sdkKey, config);
+ @SuppressWarnings("deprecation") // defaultUpdateProcessor will be replaced by streamingDataSource once the deprecated config.stream is removed
UpdateProcessorFactory upFactory = config.dataSourceFactory == null ?
- Components.defaultDataSource() : config.dataSourceFactory;
+ Components.defaultUpdateProcessor() : config.dataSourceFactory;
this.updateProcessor = upFactory.createUpdateProcessor(sdkKey, config, featureStore);
Future startFuture = updateProcessor.start();
if (config.startWaitMillis > 0L) {
- if (!config.offline && !config.useLdd) {
+ if (!(updateProcessor instanceof NullUpdateProcessor)) {
logger.info("Waiting up to " + config.startWaitMillis + " milliseconds for LaunchDarkly client to start...");
}
try {
diff --git a/src/main/java/com/launchdarkly/client/LDConfig.java b/src/main/java/com/launchdarkly/client/LDConfig.java
index bc73cfb14..fedbbc94a 100644
--- a/src/main/java/com/launchdarkly/client/LDConfig.java
+++ b/src/main/java/com/launchdarkly/client/LDConfig.java
@@ -2,6 +2,8 @@
import com.google.gson.Gson;
import com.google.gson.GsonBuilder;
+import com.launchdarkly.client.integrations.PollingDataSourceBuilder;
+import com.launchdarkly.client.integrations.StreamingDataSourceBuilder;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
@@ -31,25 +33,25 @@ public final class LDConfig {
private static final Logger logger = LoggerFactory.getLogger(LDConfig.class);
final Gson gson = new GsonBuilder().registerTypeAdapter(LDUser.class, new LDUser.UserAdapterWithPrivateAttributeBehavior(this)).create();
- private static final URI DEFAULT_BASE_URI = URI.create("https://app.launchdarkly.com");
- private static final URI DEFAULT_EVENTS_URI = URI.create("https://events.launchdarkly.com");
- private static final URI DEFAULT_STREAM_URI = URI.create("https://stream.launchdarkly.com");
+ static final URI DEFAULT_BASE_URI = URI.create("https://app.launchdarkly.com");
+ static final URI DEFAULT_EVENTS_URI = URI.create("https://events.launchdarkly.com");
+ static final URI DEFAULT_STREAM_URI = URI.create("https://stream.launchdarkly.com");
private static final int DEFAULT_CAPACITY = 10000;
private static final int DEFAULT_CONNECT_TIMEOUT_MILLIS = 2000;
private static final int DEFAULT_SOCKET_TIMEOUT_MILLIS = 10000;
private static final int DEFAULT_FLUSH_INTERVAL_SECONDS = 5;
- private static final long MIN_POLLING_INTERVAL_MILLIS = 30000L;
+ private static final long MIN_POLLING_INTERVAL_MILLIS = PollingDataSourceBuilder.DEFAULT_POLL_INTERVAL_MILLIS;
private static final long DEFAULT_START_WAIT_MILLIS = 5000L;
private static final int DEFAULT_SAMPLING_INTERVAL = 0;
private static final int DEFAULT_USER_KEYS_CAPACITY = 1000;
private static final int DEFAULT_USER_KEYS_FLUSH_INTERVAL_SECONDS = 60 * 5;
- private static final long DEFAULT_RECONNECT_TIME_MILLIS = 1000;
+ private static final long DEFAULT_RECONNECT_TIME_MILLIS = StreamingDataSourceBuilder.DEFAULT_INITIAL_RECONNECT_DELAY_MILLIS;
protected static final LDConfig DEFAULT = new Builder().build();
- final URI baseURI;
+ final URI deprecatedBaseURI;
final URI eventsURI;
- final URI streamURI;
+ final URI deprecatedStreamURI;
final int capacity;
final int flushInterval;
final Proxy proxy;
@@ -59,15 +61,14 @@ public final class LDConfig {
final FeatureStoreFactory dataStoreFactory;
final EventProcessorFactory eventProcessorFactory;
final UpdateProcessorFactory dataSourceFactory;
- final boolean useLdd;
final boolean offline;
final boolean allAttributesPrivate;
final Set privateAttrNames;
final boolean sendEvents;
- final long pollingIntervalMillis;
+ final long deprecatedPollingIntervalMillis;
final long startWaitMillis;
final int samplingInterval;
- final long reconnectTimeMs;
+ final long deprecatedReconnectTimeMs;
final int userKeysCapacity;
final int userKeysFlushInterval;
final boolean inlineUsersInEvents;
@@ -79,31 +80,30 @@ public final class LDConfig {
final TimeUnit socketTimeoutUnit;
protected LDConfig(Builder builder) {
- this.baseURI = builder.baseURI;
+ this.deprecatedBaseURI = builder.baseURI;
this.eventsURI = builder.eventsURI;
this.capacity = builder.capacity;
this.flushInterval = builder.flushIntervalSeconds;
this.proxy = builder.proxy();
this.proxyAuthenticator = builder.proxyAuthenticator();
- this.streamURI = builder.streamURI;
+ this.deprecatedStreamURI = builder.streamURI;
this.stream = builder.stream;
this.deprecatedFeatureStore = builder.featureStore;
this.dataStoreFactory = builder.dataStoreFactory;
this.eventProcessorFactory = builder.eventProcessorFactory;
this.dataSourceFactory = builder.dataSourceFactory;
- this.useLdd = builder.useLdd;
this.offline = builder.offline;
this.allAttributesPrivate = builder.allAttributesPrivate;
this.privateAttrNames = new HashSet<>(builder.privateAttrNames);
this.sendEvents = builder.sendEvents;
if (builder.pollingIntervalMillis < MIN_POLLING_INTERVAL_MILLIS) {
- this.pollingIntervalMillis = MIN_POLLING_INTERVAL_MILLIS;
+ this.deprecatedPollingIntervalMillis = MIN_POLLING_INTERVAL_MILLIS;
} else {
- this.pollingIntervalMillis = builder.pollingIntervalMillis;
+ this.deprecatedPollingIntervalMillis = builder.pollingIntervalMillis;
}
this.startWaitMillis = builder.startWaitMillis;
this.samplingInterval = builder.samplingInterval;
- this.reconnectTimeMs = builder.reconnectTimeMillis;
+ this.deprecatedReconnectTimeMs = builder.reconnectTimeMillis;
this.userKeysCapacity = builder.userKeysCapacity;
this.userKeysFlushInterval = builder.userKeysFlushInterval;
this.inlineUsersInEvents = builder.inlineUsersInEvents;
@@ -149,15 +149,14 @@ public static class Builder {
private String proxyUsername = null;
private String proxyPassword = null;
private boolean stream = true;
- private boolean useLdd = false;
private boolean offline = false;
private boolean allAttributesPrivate = false;
private boolean sendEvents = true;
private long pollingIntervalMillis = MIN_POLLING_INTERVAL_MILLIS;
private FeatureStore featureStore = null;
- private FeatureStoreFactory dataStoreFactory = Components.inMemoryDataStore();
- private EventProcessorFactory eventProcessorFactory = Components.defaultEventProcessor();
- private UpdateProcessorFactory dataSourceFactory = Components.defaultDataSource();
+ private FeatureStoreFactory dataStoreFactory = null;
+ private EventProcessorFactory eventProcessorFactory = null;
+ private UpdateProcessorFactory dataSourceFactory = null;
private long startWaitMillis = DEFAULT_START_WAIT_MILLIS;
private int samplingInterval = DEFAULT_SAMPLING_INTERVAL;
private long reconnectTimeMillis = DEFAULT_RECONNECT_TIME_MILLIS;
@@ -175,11 +174,17 @@ public Builder() {
}
/**
- * Set the base URL of the LaunchDarkly server for this configuration.
+ * Deprecated method for setting the base URI for the polling service.
+ *
+ * This method has no effect if you have used {@link #dataSource(UpdateProcessorFactory)} to
+ * specify polling or streaming options, which is the preferred method.
*
* @param baseURI the base URL of the LaunchDarkly server for this configuration.
* @return the builder
+ * @deprecated Use {@link Components#streamingDataSource()} with {@link StreamingDataSourceBuilder#pollingBaseUri(URI)},
+ * or {@link Components#pollingDataSource()} with {@link PollingDataSourceBuilder#baseUri(URI)}.
*/
+ @Deprecated
public Builder baseURI(URI baseURI) {
this.baseURI = baseURI;
return this;
@@ -197,11 +202,16 @@ public Builder eventsURI(URI eventsURI) {
}
/**
- * Set the base URL of the LaunchDarkly streaming server for this configuration.
+ * Deprecated method for setting the base URI for the streaming service.
+ *
+ * This method has no effect if you have used {@link #dataSource(UpdateProcessorFactory)} to
+ * specify polling or streaming options, which is the preferred method.
*
* @param streamURI the base URL of the LaunchDarkly streaming server
* @return the builder
+ * @deprecated Use {@link Components#streamingDataSource()} with {@link StreamingDataSourceBuilder#pollingBaseUri(URI)}.
*/
+ @Deprecated
public Builder streamURI(URI streamURI) {
this.streamURI = streamURI;
return this;
@@ -218,7 +228,7 @@ public Builder streamURI(URI streamURI) {
*
* @param factory the factory object
* @return the builder
- * @since 4.11.0
+ * @since 4.12.0
*/
public Builder dataStore(FeatureStoreFactory factory) {
this.dataStoreFactory = factory;
@@ -257,7 +267,7 @@ public Builder featureStoreFactory(FeatureStoreFactory factory) {
* you may choose to use a custom implementation (for instance, a test fixture).
* @param factory the factory object
* @return the builder
- * @since 4.11.0
+ * @since 4.12.0
*/
public Builder eventProcessor(EventProcessorFactory factory) {
this.eventProcessorFactory = factory;
@@ -278,15 +288,20 @@ public Builder eventProcessorFactory(EventProcessorFactory factory) {
/**
* Sets the implementation of the component that receives feature flag data from LaunchDarkly,
- * using a factory object. The default is {@link Components#defaultDataSource()}, but
- * you may choose to use a custom implementation (for instance, a test fixture).
- *
+ * using a factory object. Depending on the implementation, the factory may be a builder that
+ * allows you to set other configuration options as well.
+ *
+ * The default is {@link Components#streamingDataSource()}. You may instead use
+ * {@link Components#pollingDataSource()}, or a test fixture such as
+ * {@link com.launchdarkly.client.integrations.FileData#dataSource()}. See those methods
+ * for details on how to configure them.
+ *
* Note that the interface is still named {@link UpdateProcessorFactory}, but in a future version
* it will be renamed to {@code DataSourceFactory}.
*
* @param factory the factory object
* @return the builder
- * @since 4.11.0
+ * @since 4.12.0
*/
public Builder dataSource(UpdateProcessorFactory factory) {
this.dataSourceFactory = factory;
@@ -307,12 +322,18 @@ public Builder updateProcessorFactory(UpdateProcessorFactory factory) {
}
/**
- * Set whether streaming mode should be enabled. By default, streaming is enabled. It should only be
- * disabled on the advice of LaunchDarkly support.
- *
+ * Deprecated method for enabling or disabling streaming mode.
+ *
+ * By default, streaming is enabled. It should only be disabled on the advice of LaunchDarkly support.
+ *
+ * This method has no effect if you have specified a data source with {@link #dataSource(UpdateProcessorFactory)},
+ * which is the preferred method.
+ *
* @param stream whether streaming mode should be enabled
* @return the builder
+ * @deprecated Use {@link Components#streamingDataSource()} or {@link Components#pollingDataSource()}.
*/
+ @Deprecated
public Builder stream(boolean stream) {
this.stream = stream;
return this;
@@ -465,20 +486,34 @@ public Builder sslSocketFactory(SSLSocketFactory sslSocketFactory, X509TrustMana
}
/**
- * Set whether this client should use the LaunchDarkly
- * relay in daemon mode, versus subscribing to the streaming or polling API.
- *
+ * Deprecated method for using the LaunchDarkly Relay Proxy in daemon mode.
+ *
+ * See {@link Components#externalUpdatesOnly()} for the preferred way to do this.
+ *
* @param useLdd true to use the relay in daemon mode; false to use streaming or polling
* @return the builder
+ * @deprecated Use {@link Components#externalUpdatesOnly()}.
*/
+ @Deprecated
public Builder useLdd(boolean useLdd) {
- this.useLdd = useLdd;
- return this;
+ if (useLdd) {
+ return dataSource(Components.externalUpdatesOnly());
+ } else {
+ return dataSource(null);
+ }
}
/**
* Set whether this client is offline.
- *
+ *
+ * In offline mode, the SDK will not make network connections to LaunchDarkly for any purpose. Feature
+ * flag data will only be available if it already exists in the data store, and analytics events will
+ * not be sent.
+ *
+ * This is equivalent to calling {@code dataSource(Components.externalUpdatesOnly())} and
+ * {@code sendEvents(false)}. It overrides any other values you may have set for
+ * {@link #dataSource(UpdateProcessorFactory)} or {@link #eventProcessor(EventProcessorFactory)}.
+ *
* @param offline when set to true no calls to LaunchDarkly will be made
* @return the builder
*/
@@ -513,12 +548,18 @@ public Builder sendEvents(boolean sendEvents) {
}
/**
- * Set the polling interval (when streaming is disabled). Values less than the default of
- * 30000 will be set to the default.
+ * Deprecated method for setting the polling interval in polling mode.
+ *
+ * Values less than the default of 30000 will be set to the default.
+ *
+ * This method has no effect if you have not disabled streaming mode, or if you have specified
+ * a non-polling data source with {@link #dataSource(UpdateProcessorFactory)}.
*
* @param pollingIntervalMillis rule update polling interval in milliseconds
* @return the builder
+ * @deprecated Use {@link Components#pollingDataSource()} and {@link PollingDataSourceBuilder#pollIntervalMillis(long)}.
*/
+ @Deprecated
public Builder pollingIntervalMillis(long pollingIntervalMillis) {
this.pollingIntervalMillis = pollingIntervalMillis;
return this;
@@ -554,13 +595,16 @@ public Builder samplingInterval(int samplingInterval) {
}
/**
- * The reconnect base time in milliseconds for the streaming connection. The streaming connection
- * uses an exponential backoff algorithm (with jitter) for reconnects, but will start the backoff
- * with a value near the value specified here.
+ * Deprecated method for setting the initial reconnect delay for the streaming connection.
+ *
+ * This method has no effect if you have disabled streaming mode, or if you have specified a
+ * non-streaming data source with {@link #dataSource(UpdateProcessorFactory)}.
*
* @param reconnectTimeMs the reconnect time base value in milliseconds
* @return the builder
+ * @deprecated Use {@link Components#streamingDataSource()} and {@link StreamingDataSourceBuilder#initialReconnectDelayMillis(long)}.
*/
+ @Deprecated
public Builder reconnectTimeMs(long reconnectTimeMs) {
this.reconnectTimeMillis = reconnectTimeMs;
return this;
diff --git a/src/main/java/com/launchdarkly/client/PollingProcessor.java b/src/main/java/com/launchdarkly/client/PollingProcessor.java
index 436fa4659..6cc20cb87 100644
--- a/src/main/java/com/launchdarkly/client/PollingProcessor.java
+++ b/src/main/java/com/launchdarkly/client/PollingProcessor.java
@@ -17,19 +17,19 @@
import static com.launchdarkly.client.Util.httpErrorMessage;
import static com.launchdarkly.client.Util.isHttpErrorRecoverable;
-class PollingProcessor implements UpdateProcessor {
+final class PollingProcessor implements UpdateProcessor {
private static final Logger logger = LoggerFactory.getLogger(PollingProcessor.class);
private final FeatureRequestor requestor;
- private final LDConfig config;
private final FeatureStore store;
+ private final long pollIntervalMillis;
private AtomicBoolean initialized = new AtomicBoolean(false);
private ScheduledExecutorService scheduler = null;
- PollingProcessor(LDConfig config, FeatureRequestor requestor, FeatureStore featureStore) {
- this.requestor = requestor;
- this.config = config;
+ PollingProcessor(FeatureRequestor requestor, FeatureStore featureStore, long pollIntervalMillis) {
+ this.requestor = requestor; // note that HTTP configuration is applied to the requestor when it is created
this.store = featureStore;
+ this.pollIntervalMillis = pollIntervalMillis;
}
@Override
@@ -47,7 +47,7 @@ public void close() throws IOException {
@Override
public Future start() {
logger.info("Starting LaunchDarkly polling client with interval: "
- + config.pollingIntervalMillis + " milliseconds");
+ + pollIntervalMillis + " milliseconds");
final SettableFuture initFuture = SettableFuture.create();
ThreadFactory threadFactory = new ThreadFactoryBuilder()
.setNameFormat("LaunchDarkly-PollingProcessor-%d")
@@ -75,7 +75,7 @@ public void run() {
logger.debug(e.toString(), e);
}
}
- }, 0L, config.pollingIntervalMillis, TimeUnit.MILLISECONDS);
+ }, 0L, pollIntervalMillis, TimeUnit.MILLISECONDS);
return initFuture;
}
diff --git a/src/main/java/com/launchdarkly/client/SemanticVersion.java b/src/main/java/com/launchdarkly/client/SemanticVersion.java
index 29a124fd4..7e0ef034c 100644
--- a/src/main/java/com/launchdarkly/client/SemanticVersion.java
+++ b/src/main/java/com/launchdarkly/client/SemanticVersion.java
@@ -7,7 +7,7 @@
* Simple implementation of semantic version parsing and comparison according to the Semantic
* Versions 2.0.0 standard (http://semver.org).
*/
-class SemanticVersion implements Comparable {
+final class SemanticVersion implements Comparable {
private static Pattern VERSION_REGEX = Pattern.compile(
"^(?0|[1-9]\\d*)(\\.(?0|[1-9]\\d*))?(\\.(?0|[1-9]\\d*))?" +
diff --git a/src/main/java/com/launchdarkly/client/StreamProcessor.java b/src/main/java/com/launchdarkly/client/StreamProcessor.java
index ce76e09ac..389eb6e21 100644
--- a/src/main/java/com/launchdarkly/client/StreamProcessor.java
+++ b/src/main/java/com/launchdarkly/client/StreamProcessor.java
@@ -36,8 +36,10 @@ final class StreamProcessor implements UpdateProcessor {
private static final int DEAD_CONNECTION_INTERVAL_MS = 300 * 1000;
private final FeatureStore store;
- private final LDConfig config;
private final String sdkKey;
+ private final LDConfig config;
+ private final URI streamUri;
+ private final long initialReconnectDelayMillis;
private final FeatureRequestor requestor;
private final EventSourceCreator eventSourceCreator;
private volatile EventSource es;
@@ -46,16 +48,26 @@ final class StreamProcessor implements UpdateProcessor {
ConnectionErrorHandler connectionErrorHandler = createDefaultConnectionErrorHandler(); // exposed for testing
public static interface EventSourceCreator {
- EventSource createEventSource(LDConfig config, EventHandler handler, URI streamUri, ConnectionErrorHandler errorHandler, Headers headers);
+ EventSource createEventSource(LDConfig config, EventHandler handler, URI streamUri, long initialReconnectDelayMillis,
+ ConnectionErrorHandler errorHandler, Headers headers);
}
- StreamProcessor(String sdkKey, LDConfig config, FeatureRequestor requestor, FeatureStore featureStore,
- EventSourceCreator eventSourceCreator) {
+ StreamProcessor(
+ String sdkKey,
+ LDConfig config,
+ FeatureRequestor requestor,
+ FeatureStore featureStore,
+ EventSourceCreator eventSourceCreator,
+ URI streamUri,
+ long initialReconnectDelayMillis
+ ) {
this.store = featureStore;
- this.config = config;
this.sdkKey = sdkKey;
+ this.config = config; // this is no longer the source of truth for streamUri or initialReconnectDelayMillis, but it can affect HTTP behavior
this.requestor = requestor;
this.eventSourceCreator = eventSourceCreator != null ? eventSourceCreator : new DefaultEventSourceCreator();
+ this.streamUri = streamUri;
+ this.initialReconnectDelayMillis = initialReconnectDelayMillis;
}
private ConnectionErrorHandler createDefaultConnectionErrorHandler() {
@@ -191,7 +203,8 @@ public void onError(Throwable throwable) {
};
es = eventSourceCreator.createEventSource(config, handler,
- URI.create(config.streamURI.toASCIIString() + "/all"),
+ URI.create(streamUri.toASCIIString() + "/all"),
+ initialReconnectDelayMillis,
wrappedConnectionErrorHandler,
headers);
es.start();
@@ -218,31 +231,29 @@ public boolean initialized() {
private static final class PutData {
FeatureRequestor.AllData data;
- public PutData() {
-
- }
+ @SuppressWarnings("unused") // used by Gson
+ public PutData() { }
}
private static final class PatchData {
String path;
JsonElement data;
- public PatchData() {
-
- }
+ @SuppressWarnings("unused") // used by Gson
+ public PatchData() { }
}
private static final class DeleteData {
String path;
int version;
- public DeleteData() {
-
- }
+ @SuppressWarnings("unused") // used by Gson
+ public DeleteData() { }
}
private class DefaultEventSourceCreator implements EventSourceCreator {
- public EventSource createEventSource(final LDConfig config, EventHandler handler, URI streamUri, ConnectionErrorHandler errorHandler, Headers headers) {
+ public EventSource createEventSource(final LDConfig config, EventHandler handler, URI streamUri, long initialReconnectDelayMillis,
+ ConnectionErrorHandler errorHandler, Headers headers) {
EventSource.Builder builder = new EventSource.Builder(handler, streamUri)
.clientBuilderActions(new EventSource.Builder.ClientConfigurer() {
public void configure(OkHttpClient.Builder builder) {
@@ -251,7 +262,7 @@ public void configure(OkHttpClient.Builder builder) {
})
.connectionErrorHandler(errorHandler)
.headers(headers)
- .reconnectTimeMs(config.reconnectTimeMs)
+ .reconnectTimeMs(initialReconnectDelayMillis)
.readTimeoutMs(DEAD_CONNECTION_INTERVAL_MS)
.connectTimeoutMs(EventSource.DEFAULT_CONNECT_TIMEOUT_MS)
.writeTimeoutMs(EventSource.DEFAULT_WRITE_TIMEOUT_MS);
diff --git a/src/main/java/com/launchdarkly/client/UpdateProcessor.java b/src/main/java/com/launchdarkly/client/UpdateProcessor.java
index 15cc7231e..52bd712ce 100644
--- a/src/main/java/com/launchdarkly/client/UpdateProcessor.java
+++ b/src/main/java/com/launchdarkly/client/UpdateProcessor.java
@@ -33,8 +33,10 @@ public interface UpdateProcessor extends Closeable {
/**
* An implementation of {@link UpdateProcessor} that does nothing.
*
- * @deprecated Use {@link Components#nullUpdateProcessor()} instead of referring to this implementation class directly.
+ * @deprecated Use {@link Components#externalUpdatesOnly()} instead of referring to this implementation class directly.
*/
+ // This was exposed because everything in an interface is public. The SDK itself no longer refers to this class;
+ // instead it uses Components.NullUpdateProcessor.
@Deprecated
static final class NullUpdateProcessor implements UpdateProcessor {
@Override
diff --git a/src/main/java/com/launchdarkly/client/integrations/CacheMonitor.java b/src/main/java/com/launchdarkly/client/integrations/CacheMonitor.java
index 618edc63d..977982d9f 100644
--- a/src/main/java/com/launchdarkly/client/integrations/CacheMonitor.java
+++ b/src/main/java/com/launchdarkly/client/integrations/CacheMonitor.java
@@ -7,7 +7,7 @@
* A conduit that an application can use to monitor caching behavior of a persistent data store.
*
* @see PersistentDataStoreBuilder#cacheMonitor(CacheMonitor)
- * @since 4.11.0
+ * @since 4.12.0
*/
public final class CacheMonitor {
private Callable source;
@@ -48,7 +48,7 @@ public CacheStats getCacheStats() {
* internally, but is not guaranteed to always do so, and to avoid embedding Guava API details in
* the SDK API this is provided as a separate class.
*
- * @since 4.11.0
+ * @since 4.12.0
*/
public static final class CacheStats {
private final long hitCount;
diff --git a/src/main/java/com/launchdarkly/client/integrations/FileData.java b/src/main/java/com/launchdarkly/client/integrations/FileData.java
index a6f65f3e2..9771db552 100644
--- a/src/main/java/com/launchdarkly/client/integrations/FileData.java
+++ b/src/main/java/com/launchdarkly/client/integrations/FileData.java
@@ -7,7 +7,7 @@
* typically be used in a test environment, to operate using a predetermined feature flag state
* without an actual LaunchDarkly connection. See {@link #dataSource()} for details.
*
- * @since 4.11.0
+ * @since 4.12.0
*/
public abstract class FileData {
/**
@@ -106,7 +106,7 @@ public abstract class FileData {
* duplicate key-- it will not load flags from any of the files.
*
* @return a data source configuration object
- * @since 4.11.0
+ * @since 4.12.0
*/
public static FileDataSourceBuilder dataSource() {
return new FileDataSourceBuilder();
diff --git a/src/main/java/com/launchdarkly/client/integrations/FileDataSourceBuilder.java b/src/main/java/com/launchdarkly/client/integrations/FileDataSourceBuilder.java
index 4c3cd1993..49df9e3de 100644
--- a/src/main/java/com/launchdarkly/client/integrations/FileDataSourceBuilder.java
+++ b/src/main/java/com/launchdarkly/client/integrations/FileDataSourceBuilder.java
@@ -18,7 +18,7 @@
*
* For more details, see {@link FileData}.
*
- * @since 4.11.0
+ * @since 4.12.0
*/
public final class FileDataSourceBuilder implements UpdateProcessorFactory {
private final List sources = new ArrayList<>();
diff --git a/src/main/java/com/launchdarkly/client/integrations/PersistentDataStoreBuilder.java b/src/main/java/com/launchdarkly/client/integrations/PersistentDataStoreBuilder.java
index 43a1b42b3..f3c5a9261 100644
--- a/src/main/java/com/launchdarkly/client/integrations/PersistentDataStoreBuilder.java
+++ b/src/main/java/com/launchdarkly/client/integrations/PersistentDataStoreBuilder.java
@@ -33,7 +33,7 @@
* In this example, {@code .url()} is an option specifically for the Redis integration, whereas
* {@code ttlSeconds()} is an option that can be used for any persistent data store.
*
- * @since 4.11.0
+ * @since 4.12.0
*/
@SuppressWarnings("deprecation")
public final class PersistentDataStoreBuilder implements FeatureStoreFactory {
diff --git a/src/main/java/com/launchdarkly/client/integrations/PollingDataSourceBuilder.java b/src/main/java/com/launchdarkly/client/integrations/PollingDataSourceBuilder.java
new file mode 100644
index 000000000..7999b63c1
--- /dev/null
+++ b/src/main/java/com/launchdarkly/client/integrations/PollingDataSourceBuilder.java
@@ -0,0 +1,73 @@
+package com.launchdarkly.client.integrations;
+
+import com.launchdarkly.client.Components;
+import com.launchdarkly.client.UpdateProcessorFactory;
+
+import java.net.URI;
+
+/**
+ * Contains methods for configuring the polling data source.
+ *
+ * Polling is not the default behavior; by default, the SDK uses a streaming connection to receive feature flag
+ * data from LaunchDarkly. In polling mode, the SDK instead makes a new HTTP request to LaunchDarkly at regular
+ * intervals. HTTP caching allows it to avoid redundantly downloading data if there have been no changes, but
+ * polling is still less efficient than streaming and should only be used on the advice of LaunchDarkly support.
+ *
+ * To use polling mode, create a builder with {@link Components#pollingDataSource()},
+ * change its properties with the methods of this class, and pass it to {@link com.launchdarkly.client.LDConfig.Builder#dataSource(UpdateProcessorFactory)}:
+ *
+ * LDConfig config = new LDConfig.Builder()
+ * .dataSource(Components.pollingDataSource().pollIntervalMillis(45000))
+ * .build();
+ *
+ *
+ * These properties will override any equivalent deprecated properties that were set with {@code LDConfig.Builder},
+ * such as {@link com.launchdarkly.client.LDConfig.Builder#pollingIntervalMillis(long)}.
+ *
+ * Note that this class is abstract; the actual implementation is created by calling {@link Components#pollingDataSource()}.
+ *
+ * @since 4.12.0
+ */
+public abstract class PollingDataSourceBuilder implements UpdateProcessorFactory {
+ /**
+ * The default and minimum value for {@link #pollIntervalMillis(long)}.
+ */
+ public static final long DEFAULT_POLL_INTERVAL_MILLIS = 30000L;
+
+ protected URI baseUri;
+ protected long pollIntervalMillis = DEFAULT_POLL_INTERVAL_MILLIS;
+
+ /**
+ * Sets a custom base URI for the polling service.
+ *
+ * You will only need to change this value in the following cases:
+ *
+ * - You are using the Relay Proxy. Set
+ * {@code streamUri} to the base URI of the Relay Proxy instance.
+ *
- You are connecting to a test server or anything else other than the standard LaunchDarkly service.
+ *
+ *
+ * @param baseUri the base URI of the polling service; null to use the default
+ * @return the builder
+ */
+ public PollingDataSourceBuilder baseUri(URI baseUri) {
+ this.baseUri = baseUri;
+ return this;
+ }
+
+ /**
+ * Sets the interval at which the SDK will poll for feature flag updates.
+ *
+ * The default and minimum value is {@link #DEFAULT_POLL_INTERVAL_MILLIS}. Values less than this will be
+ * set to the default.
+ *
+ * @param pollIntervalMillis the polling interval in milliseconds
+ * @return the builder
+ */
+ public PollingDataSourceBuilder pollIntervalMillis(long pollIntervalMillis) {
+ this.pollIntervalMillis = pollIntervalMillis < DEFAULT_POLL_INTERVAL_MILLIS ?
+ DEFAULT_POLL_INTERVAL_MILLIS :
+ pollIntervalMillis;
+ return this;
+ }
+}
diff --git a/src/main/java/com/launchdarkly/client/integrations/Redis.java b/src/main/java/com/launchdarkly/client/integrations/Redis.java
index 7a167ae9d..90bed1e00 100644
--- a/src/main/java/com/launchdarkly/client/integrations/Redis.java
+++ b/src/main/java/com/launchdarkly/client/integrations/Redis.java
@@ -3,7 +3,7 @@
/**
* Integration between the LaunchDarkly SDK and Redis.
*
- * @since 4.11.0
+ * @since 4.12.0
*/
public abstract class Redis {
/**
diff --git a/src/main/java/com/launchdarkly/client/integrations/RedisDataStoreBuilder.java b/src/main/java/com/launchdarkly/client/integrations/RedisDataStoreBuilder.java
index 70b25b792..5c63ca819 100644
--- a/src/main/java/com/launchdarkly/client/integrations/RedisDataStoreBuilder.java
+++ b/src/main/java/com/launchdarkly/client/integrations/RedisDataStoreBuilder.java
@@ -23,16 +23,18 @@
* Builder calls can be chained, for example:
*
*
- * LDConfig config = new LDConfig.Builder()
- * .dataStore(
- * Redis.dataStore()
- * .database(1)
- * .caching(FeatureStoreCacheConfig.enabled().ttlSeconds(60))
- * )
- * .build();
+ * LDConfig config = new LDConfig.Builder()
+ * .dataStore(
+ * Components.persistentDataStore(
+ * Redis.dataStore()
+ * .url("redis://my-redis-host")
+ * .database(1)
+ * ).cacheSeconds(15)
+ * )
+ * .build();
*
*
- * @since 4.11.0
+ * @since 4.12.0
*/
public final class RedisDataStoreBuilder implements PersistentDataStoreFactory {
/**
diff --git a/src/main/java/com/launchdarkly/client/integrations/RedisDataStoreImpl.java b/src/main/java/com/launchdarkly/client/integrations/RedisDataStoreImpl.java
index 24e3968b7..c81a02762 100644
--- a/src/main/java/com/launchdarkly/client/integrations/RedisDataStoreImpl.java
+++ b/src/main/java/com/launchdarkly/client/integrations/RedisDataStoreImpl.java
@@ -22,7 +22,7 @@
import redis.clients.jedis.Transaction;
import redis.clients.util.JedisURIHelper;
-class RedisDataStoreImpl implements FeatureStoreCore {
+final class RedisDataStoreImpl implements FeatureStoreCore {
private static final Logger logger = LoggerFactory.getLogger(RedisDataStoreImpl.class);
private final JedisPool pool;
diff --git a/src/main/java/com/launchdarkly/client/integrations/StreamingDataSourceBuilder.java b/src/main/java/com/launchdarkly/client/integrations/StreamingDataSourceBuilder.java
new file mode 100644
index 000000000..b62ca4367
--- /dev/null
+++ b/src/main/java/com/launchdarkly/client/integrations/StreamingDataSourceBuilder.java
@@ -0,0 +1,88 @@
+package com.launchdarkly.client.integrations;
+
+import com.launchdarkly.client.Components;
+import com.launchdarkly.client.UpdateProcessorFactory;
+
+import java.net.URI;
+
+/**
+ * Contains methods for configuring the streaming data source.
+ *
+ * By default, the SDK uses a streaming connection to receive feature flag data from LaunchDarkly. If you want
+ * to customize the behavior of the connection, create a builder with {@link Components#streamingDataSource()},
+ * change its properties with the methods of this class, and pass it to {@link com.launchdarkly.client.LDConfig.Builder#dataSource(UpdateProcessorFactory)}:
+ *
+ * LDConfig config = new LDConfig.Builder()
+ * .dataSource(Components.streamingDataSource().initialReconnectDelayMillis(500))
+ * .build();
+ *
+ *
+ * These properties will override any equivalent deprecated properties that were set with {@code LDConfig.Builder},
+ * such as {@link com.launchdarkly.client.LDConfig.Builder#reconnectTimeMs(long)}.
+ *
+ * Note that this class is abstract; the actual implementation is created by calling {@link Components#streamingDataSource()}.
+ *
+ * @since 4.12.0
+ */
+public abstract class StreamingDataSourceBuilder implements UpdateProcessorFactory {
+ /**
+ * The default value for {@link #initialReconnectDelayMillis(long)}.
+ */
+ public static final long DEFAULT_INITIAL_RECONNECT_DELAY_MILLIS = 1000;
+
+ protected URI baseUri;
+ protected URI pollingBaseUri;
+ protected long initialReconnectDelayMillis = DEFAULT_INITIAL_RECONNECT_DELAY_MILLIS;
+
+ /**
+ * Sets a custom base URI for the streaming service.
+ *
+ * You will only need to change this value in the following cases:
+ *
+ * - You are using the Relay Proxy. Set
+ * {@code baseUri} to the base URI of the Relay Proxy instance.
+ *
- You are connecting to a test server or a nonstandard endpoint for the LaunchDarkly service.
+ *
+ *
+ * @param baseUri the base URI of the streaming service; null to use the default
+ * @return the builder
+ */
+ public StreamingDataSourceBuilder baseUri(URI baseUri) {
+ this.baseUri = baseUri;
+ return this;
+ }
+
+ /**
+ * Sets the initial reconnect delay for the streaming connection.
+ *
+ * The streaming service uses a backoff algorithm (with jitter) every time the connection needs
+ * to be reestablished. The delay for the first reconnection will start near this value, and then
+ * increase exponentially for any subsequent connection failures.
+ *
+ * The default value is {@link #DEFAULT_INITIAL_RECONNECT_DELAY_MILLIS}.
+ *
+ * @param initialReconnectDelayMillis the reconnect time base value in milliseconds
+ * @return the builder
+ */
+
+ public StreamingDataSourceBuilder initialReconnectDelayMillis(long initialReconnectDelayMillis) {
+ this.initialReconnectDelayMillis = initialReconnectDelayMillis;
+ return this;
+ }
+
+ /**
+ * Sets a custom base URI for special polling requests.
+ *
+ * Even in streaming mode, the SDK sometimes temporarily must do a polling request. You do not need to
+ * modify this property unless you are connecting to a test server or a nonstandard endpoing for the
+ * LaunchDarkly service. If you are using the Relay Proxy,
+ * you only need to set {@link #baseUri(URI)}.
+ *
+ * @param pollingBaseUri the polling endpoint URI; null to use the default
+ * @return the builder
+ */
+ public StreamingDataSourceBuilder pollingBaseUri(URI pollingBaseUri) {
+ this.pollingBaseUri = pollingBaseUri;
+ return this;
+ }
+}
diff --git a/src/main/java/com/launchdarkly/client/integrations/package-info.java b/src/main/java/com/launchdarkly/client/integrations/package-info.java
index 589a2c63a..079858106 100644
--- a/src/main/java/com/launchdarkly/client/integrations/package-info.java
+++ b/src/main/java/com/launchdarkly/client/integrations/package-info.java
@@ -1,5 +1,6 @@
/**
- * This package contains integration tools for connecting the SDK to other software components.
+ * This package contains integration tools for connecting the SDK to other software components, or
+ * configuring how it connects to LaunchDarkly.
*
* In the current main LaunchDarkly Java SDK library, this package contains {@link com.launchdarkly.client.integrations.Redis}
* (for using Redis as a store for flag data) and {@link com.launchdarkly.client.integrations.FileData}
diff --git a/src/main/java/com/launchdarkly/client/interfaces/PersistentDataStoreFactory.java b/src/main/java/com/launchdarkly/client/interfaces/PersistentDataStoreFactory.java
index 8931247d3..16a5b5544 100644
--- a/src/main/java/com/launchdarkly/client/interfaces/PersistentDataStoreFactory.java
+++ b/src/main/java/com/launchdarkly/client/interfaces/PersistentDataStoreFactory.java
@@ -10,7 +10,7 @@
* {@link com.launchdarkly.client.Components#persistentDataStore}.
*
* @see com.launchdarkly.client.Components
- * @since 4.11.0
+ * @since 4.12.0
*/
public interface PersistentDataStoreFactory {
/**
diff --git a/src/test/java/com/launchdarkly/client/FeatureRequestorTest.java b/src/test/java/com/launchdarkly/client/FeatureRequestorTest.java
index b80386f10..276cbab00 100644
--- a/src/test/java/com/launchdarkly/client/FeatureRequestorTest.java
+++ b/src/test/java/com/launchdarkly/client/FeatureRequestorTest.java
@@ -8,7 +8,6 @@
import javax.net.ssl.SSLHandshakeException;
-import static com.launchdarkly.client.TestHttpUtil.baseConfig;
import static com.launchdarkly.client.TestHttpUtil.httpsServerWithSelfSignedCert;
import static com.launchdarkly.client.TestHttpUtil.jsonResponse;
import static com.launchdarkly.client.TestHttpUtil.makeStartedServer;
@@ -33,12 +32,23 @@ public class FeatureRequestorTest {
private static final String segmentsJson = "{\"" + segment1Key + "\":" + segment1Json + "}";
private static final String allDataJson = "{\"flags\":" + flagsJson + ",\"segments\":" + segmentsJson + "}";
+ private DefaultFeatureRequestor makeRequestor(MockWebServer server) {
+ return makeRequestor(server, LDConfig.DEFAULT);
+ // We can always use LDConfig.DEFAULT unless we need to modify HTTP properties, since DefaultFeatureRequestor
+ // no longer uses the deprecated LDConfig.baseUri property.
+ }
+
+ private DefaultFeatureRequestor makeRequestor(MockWebServer server, LDConfig config) {
+ URI uri = server.url("").uri();
+ return new DefaultFeatureRequestor(sdkKey, config, uri, true);
+ }
+
@Test
public void requestAllData() throws Exception {
MockResponse resp = jsonResponse(allDataJson);
try (MockWebServer server = makeStartedServer(resp)) {
- try (DefaultFeatureRequestor r = new DefaultFeatureRequestor(sdkKey, basePollingConfig(server).build())) {
+ try (DefaultFeatureRequestor r = makeRequestor(server)) {
FeatureRequestor.AllData data = r.getAllData();
RecordedRequest req = server.takeRequest();
@@ -61,7 +71,7 @@ public void requestFlag() throws Exception {
MockResponse resp = jsonResponse(flag1Json);
try (MockWebServer server = makeStartedServer(resp)) {
- try (DefaultFeatureRequestor r = new DefaultFeatureRequestor(sdkKey, basePollingConfig(server).build())) {
+ try (DefaultFeatureRequestor r = makeRequestor(server)) {
FeatureFlag flag = r.getFlag(flag1Key);
RecordedRequest req = server.takeRequest();
@@ -78,7 +88,7 @@ public void requestSegment() throws Exception {
MockResponse resp = jsonResponse(segment1Json);
try (MockWebServer server = makeStartedServer(resp)) {
- try (DefaultFeatureRequestor r = new DefaultFeatureRequestor(sdkKey, basePollingConfig(server).build())) {
+ try (DefaultFeatureRequestor r = makeRequestor(server)) {
Segment segment = r.getSegment(segment1Key);
RecordedRequest req = server.takeRequest();
@@ -95,7 +105,7 @@ public void requestFlagNotFound() throws Exception {
MockResponse notFoundResp = new MockResponse().setResponseCode(404);
try (MockWebServer server = makeStartedServer(notFoundResp)) {
- try (DefaultFeatureRequestor r = new DefaultFeatureRequestor(sdkKey, basePollingConfig(server).build())) {
+ try (DefaultFeatureRequestor r = makeRequestor(server)) {
try {
r.getFlag(flag1Key);
Assert.fail("expected exception");
@@ -111,7 +121,7 @@ public void requestSegmentNotFound() throws Exception {
MockResponse notFoundResp = new MockResponse().setResponseCode(404);
try (MockWebServer server = makeStartedServer(notFoundResp)) {
- try (DefaultFeatureRequestor r = new DefaultFeatureRequestor(sdkKey, basePollingConfig(server).build())) {
+ try (DefaultFeatureRequestor r = makeRequestor(server)) {
try {
r.getSegment(segment1Key);
fail("expected exception");
@@ -129,7 +139,7 @@ public void requestsAreCached() throws Exception {
.setHeader("Cache-Control", "max-age=1000");
try (MockWebServer server = makeStartedServer(cacheableResp)) {
- try (DefaultFeatureRequestor r = new DefaultFeatureRequestor(sdkKey, basePollingConfig(server).build())) {
+ try (DefaultFeatureRequestor r = makeRequestor(server)) {
FeatureFlag flag1a = r.getFlag(flag1Key);
RecordedRequest req1 = server.takeRequest();
@@ -150,7 +160,7 @@ public void httpClientDoesNotAllowSelfSignedCertByDefault() throws Exception {
MockResponse resp = jsonResponse(flag1Json);
try (TestHttpUtil.ServerWithCert serverWithCert = httpsServerWithSelfSignedCert(resp)) {
- try (DefaultFeatureRequestor r = new DefaultFeatureRequestor(sdkKey, basePollingConfig(serverWithCert.server).build())) {
+ try (DefaultFeatureRequestor r = makeRequestor(serverWithCert.server)) {
try {
r.getFlag(flag1Key);
fail("expected exception");
@@ -167,11 +177,11 @@ public void httpClientCanUseCustomTlsConfig() throws Exception {
MockResponse resp = jsonResponse(flag1Json);
try (TestHttpUtil.ServerWithCert serverWithCert = httpsServerWithSelfSignedCert(resp)) {
- LDConfig config = basePollingConfig(serverWithCert.server)
+ LDConfig config = new LDConfig.Builder()
.sslSocketFactory(serverWithCert.sslClient.socketFactory, serverWithCert.sslClient.trustManager) // allows us to trust the self-signed cert
.build();
- try (DefaultFeatureRequestor r = new DefaultFeatureRequestor(sdkKey, config)) {
+ try (DefaultFeatureRequestor r = makeRequestor(serverWithCert.server, config)) {
FeatureFlag flag = r.getFlag(flag1Key);
verifyFlag(flag, flag1Key);
}
@@ -184,12 +194,11 @@ public void httpClientCanUseProxyConfig() throws Exception {
try (MockWebServer server = makeStartedServer(jsonResponse(flag1Json))) {
HttpUrl serverUrl = server.url("/");
LDConfig config = new LDConfig.Builder()
- .baseURI(fakeBaseUri)
.proxyHost(serverUrl.host())
.proxyPort(serverUrl.port())
.build();
- try (DefaultFeatureRequestor r = new DefaultFeatureRequestor(sdkKey, config)) {
+ try (DefaultFeatureRequestor r = new DefaultFeatureRequestor(sdkKey, config, fakeBaseUri, true)) {
FeatureFlag flag = r.getFlag(flag1Key);
verifyFlag(flag, flag1Key);
@@ -198,11 +207,6 @@ public void httpClientCanUseProxyConfig() throws Exception {
}
}
- private LDConfig.Builder basePollingConfig(MockWebServer server) {
- return baseConfig(server)
- .stream(false);
- }
-
private void verifyHeaders(RecordedRequest req) {
assertEquals(sdkKey, req.getHeader("Authorization"));
assertEquals("JavaClient/" + LDClient.CLIENT_VERSION, req.getHeader("User-Agent"));
diff --git a/src/test/java/com/launchdarkly/client/LDClientEndToEndTest.java b/src/test/java/com/launchdarkly/client/LDClientEndToEndTest.java
index a513e9d0d..f60f3633b 100644
--- a/src/test/java/com/launchdarkly/client/LDClientEndToEndTest.java
+++ b/src/test/java/com/launchdarkly/client/LDClientEndToEndTest.java
@@ -6,7 +6,8 @@
import org.junit.Test;
-import static com.launchdarkly.client.TestHttpUtil.baseConfig;
+import static com.launchdarkly.client.TestHttpUtil.basePollingConfig;
+import static com.launchdarkly.client.TestHttpUtil.baseStreamingConfig;
import static com.launchdarkly.client.TestHttpUtil.httpsServerWithSelfSignedCert;
import static com.launchdarkly.client.TestHttpUtil.jsonResponse;
import static com.launchdarkly.client.TestHttpUtil.makeStartedServer;
@@ -31,8 +32,8 @@ public void clientStartsInPollingMode() throws Exception {
MockResponse resp = jsonResponse(makeAllDataJson());
try (MockWebServer server = makeStartedServer(resp)) {
- LDConfig config = baseConfig(server)
- .stream(false)
+ LDConfig config = new LDConfig.Builder()
+ .dataSource(basePollingConfig(server))
.sendEvents(false)
.build();
@@ -48,8 +49,8 @@ public void clientFailsInPollingModeWith401Error() throws Exception {
MockResponse resp = new MockResponse().setResponseCode(401);
try (MockWebServer server = makeStartedServer(resp)) {
- LDConfig config = baseConfig(server)
- .stream(false)
+ LDConfig config = new LDConfig.Builder()
+ .dataSource(basePollingConfig(server))
.sendEvents(false)
.build();
@@ -65,8 +66,8 @@ public void clientStartsInPollingModeWithSelfSignedCert() throws Exception {
MockResponse resp = jsonResponse(makeAllDataJson());
try (TestHttpUtil.ServerWithCert serverWithCert = httpsServerWithSelfSignedCert(resp)) {
- LDConfig config = baseConfig(serverWithCert.server)
- .stream(false)
+ LDConfig config = new LDConfig.Builder()
+ .dataSource(basePollingConfig(serverWithCert.server))
.sendEvents(false)
.sslSocketFactory(serverWithCert.sslClient.socketFactory, serverWithCert.sslClient.trustManager) // allows us to trust the self-signed cert
.build();
@@ -85,7 +86,8 @@ public void clientStartsInStreamingMode() throws Exception {
MockResponse resp = TestHttpUtil.eventStreamResponse(streamData);
try (MockWebServer server = makeStartedServer(resp)) {
- LDConfig config = baseConfig(server)
+ LDConfig config = new LDConfig.Builder()
+ .dataSource(baseStreamingConfig(server))
.sendEvents(false)
.build();
@@ -101,7 +103,8 @@ public void clientFailsInStreamingModeWith401Error() throws Exception {
MockResponse resp = new MockResponse().setResponseCode(401);
try (MockWebServer server = makeStartedServer(resp)) {
- LDConfig config = baseConfig(server)
+ LDConfig config = new LDConfig.Builder()
+ .dataSource(baseStreamingConfig(server))
.sendEvents(false)
.build();
@@ -119,7 +122,8 @@ public void clientStartsInStreamingModeWithSelfSignedCert() throws Exception {
MockResponse resp = TestHttpUtil.eventStreamResponse(streamData);
try (TestHttpUtil.ServerWithCert serverWithCert = httpsServerWithSelfSignedCert(resp)) {
- LDConfig config = baseConfig(serverWithCert.server)
+ LDConfig config = new LDConfig.Builder()
+ .dataSource(baseStreamingConfig(serverWithCert.server))
.sendEvents(false)
.sslSocketFactory(serverWithCert.sslClient.socketFactory, serverWithCert.sslClient.trustManager) // allows us to trust the self-signed cert
.build();
diff --git a/src/test/java/com/launchdarkly/client/LDClientEvaluationTest.java b/src/test/java/com/launchdarkly/client/LDClientEvaluationTest.java
index 9db225910..83321c7a7 100644
--- a/src/test/java/com/launchdarkly/client/LDClientEvaluationTest.java
+++ b/src/test/java/com/launchdarkly/client/LDClientEvaluationTest.java
@@ -36,7 +36,7 @@ public class LDClientEvaluationTest {
private LDConfig config = new LDConfig.Builder()
.dataStore(specificFeatureStore(featureStore))
.eventProcessor(Components.nullEventProcessor())
- .dataSource(Components.nullDataSource())
+ .dataSource(Components.externalUpdatesOnly())
.build();
private LDClientInterface client = new LDClient("SDK_KEY", config);
@@ -266,7 +266,7 @@ public void appropriateErrorForUnexpectedException() throws Exception {
LDConfig badConfig = new LDConfig.Builder()
.dataStore(specificFeatureStore(badFeatureStore))
.eventProcessor(Components.nullEventProcessor())
- .dataSource(Components.nullDataSource())
+ .dataSource(Components.externalUpdatesOnly())
.build();
try (LDClientInterface badClient = new LDClient("SDK_KEY", badConfig)) {
EvaluationDetail expectedResult = EvaluationDetail.fromValue(false, null,
diff --git a/src/test/java/com/launchdarkly/client/LDClientEventTest.java b/src/test/java/com/launchdarkly/client/LDClientEventTest.java
index caf90b6fe..9e77aa0ec 100644
--- a/src/test/java/com/launchdarkly/client/LDClientEventTest.java
+++ b/src/test/java/com/launchdarkly/client/LDClientEventTest.java
@@ -31,7 +31,7 @@ public class LDClientEventTest {
private LDConfig config = new LDConfig.Builder()
.dataStore(specificFeatureStore(featureStore))
.eventProcessor(specificEventProcessor(eventSink))
- .dataSource(Components.nullDataSource())
+ .dataSource(Components.externalUpdatesOnly())
.build();
private LDClientInterface client = new LDClient("SDK_KEY", config);
diff --git a/src/test/java/com/launchdarkly/client/LDClientLddModeTest.java b/src/test/java/com/launchdarkly/client/LDClientExternalUpdatesOnlyTest.java
similarity index 50%
rename from src/test/java/com/launchdarkly/client/LDClientLddModeTest.java
rename to src/test/java/com/launchdarkly/client/LDClientExternalUpdatesOnlyTest.java
index 21a142823..414fd628a 100644
--- a/src/test/java/com/launchdarkly/client/LDClientLddModeTest.java
+++ b/src/test/java/com/launchdarkly/client/LDClientExternalUpdatesOnlyTest.java
@@ -14,7 +14,51 @@
import static org.junit.Assert.assertTrue;
@SuppressWarnings("javadoc")
-public class LDClientLddModeTest {
+public class LDClientExternalUpdatesOnlyTest {
+ @Test
+ public void externalUpdatesOnlyClientHasNullUpdateProcessor() throws Exception {
+ LDConfig config = new LDConfig.Builder()
+ .dataSource(Components.externalUpdatesOnly())
+ .build();
+ try (LDClient client = new LDClient("SDK_KEY", config)) {
+ assertEquals(Components.NullUpdateProcessor.class, client.updateProcessor.getClass());
+ }
+ }
+
+ @Test
+ public void externalUpdatesOnlyClientHasDefaultEventProcessor() throws Exception {
+ LDConfig config = new LDConfig.Builder()
+ .dataSource(Components.externalUpdatesOnly())
+ .build();
+ try (LDClient client = new LDClient("SDK_KEY", config)) {
+ assertEquals(DefaultEventProcessor.class, client.eventProcessor.getClass());
+ }
+ }
+
+ @Test
+ public void externalUpdatesOnlyClientIsInitialized() throws Exception {
+ LDConfig config = new LDConfig.Builder()
+ .dataSource(Components.externalUpdatesOnly())
+ .build();
+ try (LDClient client = new LDClient("SDK_KEY", config)) {
+ assertTrue(client.initialized());
+ }
+ }
+
+ @Test
+ public void externalUpdatesOnlyClientGetsFlagFromFeatureStore() throws IOException {
+ FeatureStore testFeatureStore = initedFeatureStore();
+ LDConfig config = new LDConfig.Builder()
+ .dataSource(Components.externalUpdatesOnly())
+ .dataStore(specificFeatureStore(testFeatureStore))
+ .build();
+ FeatureFlag flag = flagWithValue("key", LDValue.of(true));
+ testFeatureStore.upsert(FEATURES, flag);
+ try (LDClient client = new LDClient("SDK_KEY", config)) {
+ assertTrue(client.boolVariation("key", new LDUser("user"), false));
+ }
+ }
+
@SuppressWarnings("deprecation")
@Test
public void lddModeClientHasNullUpdateProcessor() throws IOException {
@@ -22,12 +66,13 @@ public void lddModeClientHasNullUpdateProcessor() throws IOException {
.useLdd(true)
.build();
try (LDClient client = new LDClient("SDK_KEY", config)) {
- assertEquals(UpdateProcessor.NullUpdateProcessor.class, client.updateProcessor.getClass());
+ assertEquals(Components.NullUpdateProcessor.class, client.updateProcessor.getClass());
}
}
@Test
public void lddModeClientHasDefaultEventProcessor() throws IOException {
+ @SuppressWarnings("deprecation")
LDConfig config = new LDConfig.Builder()
.useLdd(true)
.build();
@@ -38,6 +83,7 @@ public void lddModeClientHasDefaultEventProcessor() throws IOException {
@Test
public void lddModeClientIsInitialized() throws IOException {
+ @SuppressWarnings("deprecation")
LDConfig config = new LDConfig.Builder()
.useLdd(true)
.build();
@@ -49,6 +95,7 @@ public void lddModeClientIsInitialized() throws IOException {
@Test
public void lddModeClientGetsFlagFromFeatureStore() throws IOException {
FeatureStore testFeatureStore = initedFeatureStore();
+ @SuppressWarnings("deprecation")
LDConfig config = new LDConfig.Builder()
.useLdd(true)
.dataStore(specificFeatureStore(testFeatureStore))
diff --git a/src/test/java/com/launchdarkly/client/LDClientOfflineTest.java b/src/test/java/com/launchdarkly/client/LDClientOfflineTest.java
index 2785fc5d1..20819fee0 100644
--- a/src/test/java/com/launchdarkly/client/LDClientOfflineTest.java
+++ b/src/test/java/com/launchdarkly/client/LDClientOfflineTest.java
@@ -21,14 +21,13 @@
public class LDClientOfflineTest {
private static final LDUser user = new LDUser("user");
- @SuppressWarnings("deprecation")
@Test
public void offlineClientHasNullUpdateProcessor() throws IOException {
LDConfig config = new LDConfig.Builder()
.offline(true)
.build();
try (LDClient client = new LDClient("SDK_KEY", config)) {
- assertEquals(UpdateProcessor.NullUpdateProcessor.class, client.updateProcessor.getClass());
+ assertEquals(Components.NullUpdateProcessor.class, client.updateProcessor.getClass());
}
}
diff --git a/src/test/java/com/launchdarkly/client/LDClientTest.java b/src/test/java/com/launchdarkly/client/LDClientTest.java
index ae4a20bd1..3c83cdeb9 100644
--- a/src/test/java/com/launchdarkly/client/LDClientTest.java
+++ b/src/test/java/com/launchdarkly/client/LDClientTest.java
@@ -86,8 +86,7 @@ public void constructorThrowsExceptionForNullConfig() throws Exception {
@Test
public void clientHasDefaultEventProcessorIfSendEventsIsTrue() throws Exception {
LDConfig config = new LDConfig.Builder()
- .stream(false)
- .baseURI(URI.create("/fake"))
+ .dataSource(Components.externalUpdatesOnly())
.startWaitMillis(0)
.sendEvents(true)
.build();
@@ -99,8 +98,7 @@ public void clientHasDefaultEventProcessorIfSendEventsIsTrue() throws Exception
@Test
public void clientHasNullEventProcessorIfSendEventsIsFalse() throws IOException {
LDConfig config = new LDConfig.Builder()
- .stream(false)
- .baseURI(URI.create("/fake"))
+ .dataSource(Components.externalUpdatesOnly())
.startWaitMillis(0)
.sendEvents(false)
.build();
@@ -112,8 +110,7 @@ public void clientHasNullEventProcessorIfSendEventsIsFalse() throws IOException
@Test
public void streamingClientHasStreamProcessor() throws Exception {
LDConfig config = new LDConfig.Builder()
- .stream(true)
- .streamURI(URI.create("http://fake"))
+ .dataSource(Components.streamingDataSource().baseUri(URI.create("http://fake")))
.startWaitMillis(0)
.build();
try (LDClient client = new LDClient("SDK_KEY", config)) {
@@ -124,8 +121,7 @@ public void streamingClientHasStreamProcessor() throws Exception {
@Test
public void pollingClientHasPollingProcessor() throws IOException {
LDConfig config = new LDConfig.Builder()
- .stream(false)
- .baseURI(URI.create("http://fake"))
+ .dataSource(Components.pollingDataSource().baseUri(URI.create("http://fake")))
.startWaitMillis(0)
.build();
try (LDClient client = new LDClient("SDK_KEY", config)) {
diff --git a/src/test/java/com/launchdarkly/client/LDConfigTest.java b/src/test/java/com/launchdarkly/client/LDConfigTest.java
index f7bfb689a..fbbc881d5 100644
--- a/src/test/java/com/launchdarkly/client/LDConfigTest.java
+++ b/src/test/java/com/launchdarkly/client/LDConfigTest.java
@@ -71,14 +71,16 @@ public void testProxyAuthPartialConfig() {
@Test
public void testMinimumPollingIntervalIsEnforcedProperly(){
+ @SuppressWarnings("deprecation")
LDConfig config = new LDConfig.Builder().pollingIntervalMillis(10L).build();
- assertEquals(30000L, config.pollingIntervalMillis);
+ assertEquals(30000L, config.deprecatedPollingIntervalMillis);
}
@Test
public void testPollingIntervalIsEnforcedProperly(){
+ @SuppressWarnings("deprecation")
LDConfig config = new LDConfig.Builder().pollingIntervalMillis(30001L).build();
- assertEquals(30001L, config.pollingIntervalMillis);
+ assertEquals(30001L, config.deprecatedPollingIntervalMillis);
}
@Test
diff --git a/src/test/java/com/launchdarkly/client/PollingProcessorTest.java b/src/test/java/com/launchdarkly/client/PollingProcessorTest.java
index b143cc35f..f70663fb6 100644
--- a/src/test/java/com/launchdarkly/client/PollingProcessorTest.java
+++ b/src/test/java/com/launchdarkly/client/PollingProcessorTest.java
@@ -14,13 +14,15 @@
@SuppressWarnings("javadoc")
public class PollingProcessorTest {
+ private static final long LENGTHY_INTERVAL = 60000;
+
@Test
public void testConnectionOk() throws Exception {
MockFeatureRequestor requestor = new MockFeatureRequestor();
requestor.allData = new FeatureRequestor.AllData(new HashMap(), new HashMap());
FeatureStore store = new InMemoryFeatureStore();
- try (PollingProcessor pollingProcessor = new PollingProcessor(LDConfig.DEFAULT, requestor, store)) {
+ try (PollingProcessor pollingProcessor = new PollingProcessor(requestor, store, LENGTHY_INTERVAL)) {
Future initFuture = pollingProcessor.start();
initFuture.get(1000, TimeUnit.MILLISECONDS);
assertTrue(pollingProcessor.initialized());
@@ -34,7 +36,7 @@ public void testConnectionProblem() throws Exception {
requestor.ioException = new IOException("This exception is part of a test and yes you should be seeing it.");
FeatureStore store = new InMemoryFeatureStore();
- try (PollingProcessor pollingProcessor = new PollingProcessor(LDConfig.DEFAULT, requestor, store)) {
+ try (PollingProcessor pollingProcessor = new PollingProcessor(requestor, store, LENGTHY_INTERVAL)) {
Future initFuture = pollingProcessor.start();
try {
initFuture.get(200L, TimeUnit.MILLISECONDS);
@@ -80,7 +82,7 @@ public void http500ErrorIsRecoverable() throws Exception {
private void testUnrecoverableHttpError(int status) throws Exception {
MockFeatureRequestor requestor = new MockFeatureRequestor();
requestor.httpException = new HttpErrorException(status);
- try (PollingProcessor pollingProcessor = new PollingProcessor(LDConfig.DEFAULT, requestor, new InMemoryFeatureStore())) {
+ try (PollingProcessor pollingProcessor = new PollingProcessor(requestor, new InMemoryFeatureStore(), LENGTHY_INTERVAL)) {
long startTime = System.currentTimeMillis();
Future initFuture = pollingProcessor.start();
try {
@@ -97,7 +99,7 @@ private void testUnrecoverableHttpError(int status) throws Exception {
private void testRecoverableHttpError(int status) throws Exception {
MockFeatureRequestor requestor = new MockFeatureRequestor();
requestor.httpException = new HttpErrorException(status);
- try (PollingProcessor pollingProcessor = new PollingProcessor(LDConfig.DEFAULT, requestor, new InMemoryFeatureStore())) {
+ try (PollingProcessor pollingProcessor = new PollingProcessor(requestor, new InMemoryFeatureStore(), LENGTHY_INTERVAL)) {
Future initFuture = pollingProcessor.start();
try {
initFuture.get(200, TimeUnit.MILLISECONDS);
diff --git a/src/test/java/com/launchdarkly/client/StreamProcessorTest.java b/src/test/java/com/launchdarkly/client/StreamProcessorTest.java
index ff49176c3..66608462b 100644
--- a/src/test/java/com/launchdarkly/client/StreamProcessorTest.java
+++ b/src/test/java/com/launchdarkly/client/StreamProcessorTest.java
@@ -23,7 +23,6 @@
import static com.launchdarkly.client.TestHttpUtil.eventStreamResponse;
import static com.launchdarkly.client.TestHttpUtil.makeStartedServer;
-import static com.launchdarkly.client.TestUtil.specificFeatureStore;
import static com.launchdarkly.client.VersionedDataKind.FEATURES;
import static com.launchdarkly.client.VersionedDataKind.SEGMENTS;
import static org.easymock.EasyMock.expect;
@@ -54,7 +53,6 @@ public class StreamProcessorTest extends EasyMockSupport {
"data: {\"data\":{\"flags\":{},\"segments\":{}}}\n\n";
private InMemoryFeatureStore featureStore;
- private LDConfig.Builder configBuilder;
private FeatureRequestor mockRequestor;
private EventSource mockEventSource;
private EventHandler eventHandler;
@@ -65,39 +63,37 @@ public class StreamProcessorTest extends EasyMockSupport {
@Before
public void setup() {
featureStore = new InMemoryFeatureStore();
- configBuilder = new LDConfig.Builder().dataStore(specificFeatureStore(featureStore));
mockRequestor = createStrictMock(FeatureRequestor.class);
mockEventSource = createStrictMock(EventSource.class);
}
@Test
public void streamUriHasCorrectEndpoint() {
- LDConfig config = configBuilder.streamURI(STREAM_URI).build();
- createStreamProcessor(SDK_KEY, config).start();
+ createStreamProcessor(STREAM_URI).start();
assertEquals(URI.create(STREAM_URI.toString() + "/all"), actualStreamUri);
}
@Test
public void headersHaveAuthorization() {
- createStreamProcessor(SDK_KEY, configBuilder.build()).start();
+ createStreamProcessor(STREAM_URI).start();
assertEquals(SDK_KEY, headers.get("Authorization"));
}
@Test
public void headersHaveUserAgent() {
- createStreamProcessor(SDK_KEY, configBuilder.build()).start();
+ createStreamProcessor(STREAM_URI).start();
assertEquals("JavaClient/" + LDClient.CLIENT_VERSION, headers.get("User-Agent"));
}
@Test
public void headersHaveAccept() {
- createStreamProcessor(SDK_KEY, configBuilder.build()).start();
+ createStreamProcessor(STREAM_URI).start();
assertEquals("text/event-stream", headers.get("Accept"));
}
@Test
public void putCausesFeatureToBeStored() throws Exception {
- createStreamProcessor(SDK_KEY, configBuilder.build()).start();
+ createStreamProcessor(STREAM_URI).start();
MessageEvent event = new MessageEvent("{\"data\":{\"flags\":{\"" +
FEATURE1_KEY + "\":" + featureJson(FEATURE1_KEY, FEATURE1_VERSION) + "}," +
"\"segments\":{}}}");
@@ -108,7 +104,7 @@ public void putCausesFeatureToBeStored() throws Exception {
@Test
public void putCausesSegmentToBeStored() throws Exception {
- createStreamProcessor(SDK_KEY, configBuilder.build()).start();
+ createStreamProcessor(STREAM_URI).start();
MessageEvent event = new MessageEvent("{\"data\":{\"flags\":{},\"segments\":{\"" +
SEGMENT1_KEY + "\":" + segmentJson(SEGMENT1_KEY, SEGMENT1_VERSION) + "}}}");
eventHandler.onMessage("put", event);
@@ -118,27 +114,27 @@ public void putCausesSegmentToBeStored() throws Exception {
@Test
public void storeNotInitializedByDefault() throws Exception {
- createStreamProcessor(SDK_KEY, configBuilder.build()).start();
+ createStreamProcessor(STREAM_URI).start();
assertFalse(featureStore.initialized());
}
@Test
public void putCausesStoreToBeInitialized() throws Exception {
- createStreamProcessor(SDK_KEY, configBuilder.build()).start();
+ createStreamProcessor(STREAM_URI).start();
eventHandler.onMessage("put", emptyPutEvent());
assertTrue(featureStore.initialized());
}
@Test
public void processorNotInitializedByDefault() throws Exception {
- StreamProcessor sp = createStreamProcessor(SDK_KEY, configBuilder.build());
+ StreamProcessor sp = createStreamProcessor(STREAM_URI);
sp.start();
assertFalse(sp.initialized());
}
@Test
public void putCausesProcessorToBeInitialized() throws Exception {
- StreamProcessor sp = createStreamProcessor(SDK_KEY, configBuilder.build());
+ StreamProcessor sp = createStreamProcessor(STREAM_URI);
sp.start();
eventHandler.onMessage("put", emptyPutEvent());
assertTrue(sp.initialized());
@@ -146,14 +142,14 @@ public void putCausesProcessorToBeInitialized() throws Exception {
@Test
public void futureIsNotSetByDefault() throws Exception {
- StreamProcessor sp = createStreamProcessor(SDK_KEY, configBuilder.build());
+ StreamProcessor sp = createStreamProcessor(STREAM_URI);
Future future = sp.start();
assertFalse(future.isDone());
}
@Test
public void putCausesFutureToBeSet() throws Exception {
- StreamProcessor sp = createStreamProcessor(SDK_KEY, configBuilder.build());
+ StreamProcessor sp = createStreamProcessor(STREAM_URI);
Future future = sp.start();
eventHandler.onMessage("put", emptyPutEvent());
assertTrue(future.isDone());
@@ -161,7 +157,7 @@ public void putCausesFutureToBeSet() throws Exception {
@Test
public void patchUpdatesFeature() throws Exception {
- createStreamProcessor(SDK_KEY, configBuilder.build()).start();
+ createStreamProcessor(STREAM_URI).start();
eventHandler.onMessage("put", emptyPutEvent());
String path = "/flags/" + FEATURE1_KEY;
@@ -174,7 +170,7 @@ public void patchUpdatesFeature() throws Exception {
@Test
public void patchUpdatesSegment() throws Exception {
- createStreamProcessor(SDK_KEY, configBuilder.build()).start();
+ createStreamProcessor(STREAM_URI).start();
eventHandler.onMessage("put", emptyPutEvent());
String path = "/segments/" + SEGMENT1_KEY;
@@ -187,7 +183,7 @@ public void patchUpdatesSegment() throws Exception {
@Test
public void deleteDeletesFeature() throws Exception {
- createStreamProcessor(SDK_KEY, configBuilder.build()).start();
+ createStreamProcessor(STREAM_URI).start();
eventHandler.onMessage("put", emptyPutEvent());
featureStore.upsert(FEATURES, FEATURE);
@@ -201,7 +197,7 @@ public void deleteDeletesFeature() throws Exception {
@Test
public void deleteDeletesSegment() throws Exception {
- createStreamProcessor(SDK_KEY, configBuilder.build()).start();
+ createStreamProcessor(STREAM_URI).start();
eventHandler.onMessage("put", emptyPutEvent());
featureStore.upsert(SEGMENTS, SEGMENT);
@@ -215,7 +211,7 @@ public void deleteDeletesSegment() throws Exception {
@Test
public void indirectPutRequestsAndStoresFeature() throws Exception {
- createStreamProcessor(SDK_KEY, configBuilder.build()).start();
+ createStreamProcessor(STREAM_URI).start();
setupRequestorToReturnAllDataWithFlag(FEATURE);
replayAll();
@@ -226,7 +222,7 @@ public void indirectPutRequestsAndStoresFeature() throws Exception {
@Test
public void indirectPutInitializesStore() throws Exception {
- createStreamProcessor(SDK_KEY, configBuilder.build()).start();
+ createStreamProcessor(STREAM_URI).start();
setupRequestorToReturnAllDataWithFlag(FEATURE);
replayAll();
@@ -237,7 +233,7 @@ public void indirectPutInitializesStore() throws Exception {
@Test
public void indirectPutInitializesProcessor() throws Exception {
- StreamProcessor sp = createStreamProcessor(SDK_KEY, configBuilder.build());
+ StreamProcessor sp = createStreamProcessor(STREAM_URI);
sp.start();
setupRequestorToReturnAllDataWithFlag(FEATURE);
replayAll();
@@ -249,7 +245,7 @@ public void indirectPutInitializesProcessor() throws Exception {
@Test
public void indirectPutSetsFuture() throws Exception {
- StreamProcessor sp = createStreamProcessor(SDK_KEY, configBuilder.build());
+ StreamProcessor sp = createStreamProcessor(STREAM_URI);
Future future = sp.start();
setupRequestorToReturnAllDataWithFlag(FEATURE);
replayAll();
@@ -261,7 +257,7 @@ public void indirectPutSetsFuture() throws Exception {
@Test
public void indirectPatchRequestsAndUpdatesFeature() throws Exception {
- createStreamProcessor(SDK_KEY, configBuilder.build()).start();
+ createStreamProcessor(STREAM_URI).start();
expect(mockRequestor.getFlag(FEATURE1_KEY)).andReturn(FEATURE);
replayAll();
@@ -273,7 +269,7 @@ public void indirectPatchRequestsAndUpdatesFeature() throws Exception {
@Test
public void indirectPatchRequestsAndUpdatesSegment() throws Exception {
- createStreamProcessor(SDK_KEY, configBuilder.build()).start();
+ createStreamProcessor(STREAM_URI).start();
expect(mockRequestor.getSegment(SEGMENT1_KEY)).andReturn(SEGMENT);
replayAll();
@@ -285,13 +281,13 @@ public void indirectPatchRequestsAndUpdatesSegment() throws Exception {
@Test
public void unknownEventTypeDoesNotThrowException() throws Exception {
- createStreamProcessor(SDK_KEY, configBuilder.build()).start();
+ createStreamProcessor(STREAM_URI).start();
eventHandler.onMessage("what", new MessageEvent(""));
}
@Test
public void streamWillReconnectAfterGeneralIOException() throws Exception {
- createStreamProcessor(SDK_KEY, configBuilder.build()).start();
+ createStreamProcessor(STREAM_URI).start();
ConnectionErrorHandler.Action action = errorHandler.onConnectionError(new IOException());
assertEquals(ConnectionErrorHandler.Action.PROCEED, action);
}
@@ -336,12 +332,7 @@ public void httpClientDoesNotAllowSelfSignedCertByDefault() throws Exception {
try (TestHttpUtil.ServerWithCert server = new TestHttpUtil.ServerWithCert()) {
server.server.enqueue(eventStreamResponse(STREAM_RESPONSE_WITH_EMPTY_DATA));
- LDConfig config = new LDConfig.Builder()
- .streamURI(server.uri())
- .build();
-
- try (StreamProcessor sp = new StreamProcessor("sdk-key", config,
- mockRequestor, featureStore, null)) {
+ try (StreamProcessor sp = createStreamProcessorWithRealHttp(LDConfig.DEFAULT, server.uri())) {
sp.connectionErrorHandler = errorSink;
Future ready = sp.start();
ready.get();
@@ -360,12 +351,10 @@ public void httpClientCanUseCustomTlsConfig() throws Exception {
server.server.enqueue(eventStreamResponse(STREAM_RESPONSE_WITH_EMPTY_DATA));
LDConfig config = new LDConfig.Builder()
- .streamURI(server.uri())
.sslSocketFactory(server.sslClient.socketFactory, server.sslClient.trustManager) // allows us to trust the self-signed cert
.build();
- try (StreamProcessor sp = new StreamProcessor("sdk-key", config,
- mockRequestor, featureStore, null)) {
+ try (StreamProcessor sp = createStreamProcessorWithRealHttp(config, server.uri())) {
sp.connectionErrorHandler = errorSink;
Future ready = sp.start();
ready.get();
@@ -382,13 +371,11 @@ public void httpClientCanUseProxyConfig() throws Exception {
try (MockWebServer server = makeStartedServer(eventStreamResponse(STREAM_RESPONSE_WITH_EMPTY_DATA))) {
HttpUrl serverUrl = server.url("/");
LDConfig config = new LDConfig.Builder()
- .streamURI(fakeStreamUri)
.proxyHost(serverUrl.host())
.proxyPort(serverUrl.port())
.build();
- try (StreamProcessor sp = new StreamProcessor("sdk-key", config,
- mockRequestor, featureStore, null)) {
+ try (StreamProcessor sp = createStreamProcessorWithRealHttp(config, fakeStreamUri)) {
sp.connectionErrorHandler = errorSink;
Future ready = sp.start();
ready.get();
@@ -411,7 +398,7 @@ public Action onConnectionError(Throwable t) {
private void testUnrecoverableHttpError(int status) throws Exception {
UnsuccessfulResponseException e = new UnsuccessfulResponseException(status);
long startTime = System.currentTimeMillis();
- StreamProcessor sp = createStreamProcessor(SDK_KEY, configBuilder.build());
+ StreamProcessor sp = createStreamProcessor(STREAM_URI);
Future initFuture = sp.start();
ConnectionErrorHandler.Action action = errorHandler.onConnectionError(e);
@@ -430,7 +417,7 @@ private void testUnrecoverableHttpError(int status) throws Exception {
private void testRecoverableHttpError(int status) throws Exception {
UnsuccessfulResponseException e = new UnsuccessfulResponseException(status);
long startTime = System.currentTimeMillis();
- StreamProcessor sp = createStreamProcessor(SDK_KEY, configBuilder.build());
+ StreamProcessor sp = createStreamProcessor(STREAM_URI);
Future initFuture = sp.start();
ConnectionErrorHandler.Action action = errorHandler.onConnectionError(e);
@@ -446,10 +433,20 @@ private void testRecoverableHttpError(int status) throws Exception {
assertFalse(sp.initialized());
}
- private StreamProcessor createStreamProcessor(String sdkKey, LDConfig config) {
- return new StreamProcessor(sdkKey, config, mockRequestor, featureStore, new StubEventSourceCreator());
+ private StreamProcessor createStreamProcessor(LDConfig config, URI streamUri) {
+ return new StreamProcessor(SDK_KEY, config, mockRequestor, featureStore, new StubEventSourceCreator(),
+ streamUri, config.deprecatedReconnectTimeMs);
}
-
+
+ private StreamProcessor createStreamProcessor(URI streamUri) {
+ return createStreamProcessor(LDConfig.DEFAULT, streamUri);
+ }
+
+ private StreamProcessor createStreamProcessorWithRealHttp(LDConfig config, URI streamUri) {
+ return new StreamProcessor(SDK_KEY, config, mockRequestor, featureStore, null,
+ streamUri, config.deprecatedReconnectTimeMs);
+ }
+
private String featureJson(String key, int version) {
return "{\"key\":\"" + key + "\",\"version\":" + version + ",\"on\":true}";
}
@@ -477,8 +474,8 @@ private void assertSegmentInStore(Segment segment) {
}
private class StubEventSourceCreator implements StreamProcessor.EventSourceCreator {
- public EventSource createEventSource(LDConfig config, EventHandler handler, URI streamUri, ConnectionErrorHandler errorHandler,
- Headers headers) {
+ public EventSource createEventSource(LDConfig config, EventHandler handler, URI streamUri,
+ long initialReconnectDelay, ConnectionErrorHandler errorHandler, Headers headers) {
StreamProcessorTest.this.eventHandler = handler;
StreamProcessorTest.this.actualStreamUri = streamUri;
StreamProcessorTest.this.errorHandler = errorHandler;
diff --git a/src/test/java/com/launchdarkly/client/TestHttpUtil.java b/src/test/java/com/launchdarkly/client/TestHttpUtil.java
index 8f86c7b1b..035ff814e 100644
--- a/src/test/java/com/launchdarkly/client/TestHttpUtil.java
+++ b/src/test/java/com/launchdarkly/client/TestHttpUtil.java
@@ -1,5 +1,8 @@
package com.launchdarkly.client;
+import com.launchdarkly.client.integrations.PollingDataSourceBuilder;
+import com.launchdarkly.client.integrations.StreamingDataSourceBuilder;
+
import java.io.Closeable;
import java.io.IOException;
import java.net.InetAddress;
@@ -30,12 +33,12 @@ static ServerWithCert httpsServerWithSelfSignedCert(MockResponse... responses) t
return ret;
}
- static LDConfig.Builder baseConfig(MockWebServer server) {
- URI uri = server.url("").uri();
- return new LDConfig.Builder()
- .baseURI(uri)
- .streamURI(uri)
- .eventsURI(uri);
+ static StreamingDataSourceBuilder baseStreamingConfig(MockWebServer server) {
+ return Components.streamingDataSource().baseUri(server.url("").uri());
+ }
+
+ static PollingDataSourceBuilder basePollingConfig(MockWebServer server) {
+ return Components.pollingDataSource().baseUri(server.url("").uri());
}
static MockResponse jsonResponse(String body) {