Skip to content

Commit 2a1200c

Browse files
authored
Merge branch 'main' into mzafir/jedis-username
2 parents 5057825 + 8469d3c commit 2a1200c

File tree

96 files changed

+16060
-127
lines changed

Some content is hidden

Large Commits have some content hidden by default. Use the searchbox below for content that may be hidden.

96 files changed

+16060
-127
lines changed

.release-please-manifest.json

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -2,7 +2,7 @@
22
"lib/java-server-sdk-otel": "0.2.0",
33
"lib/java-server-sdk-redis-store": "3.0.1",
44
"lib/shared/common": "2.1.2",
5-
"lib/shared/internal": "1.5.1",
5+
"lib/shared/internal": "1.6.1",
66
"lib/shared/test-helpers": "2.1.0",
77
"lib/sdk/server": "7.10.2"
88
}

lib/sdk/server/Makefile

Lines changed: 9 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -9,6 +9,9 @@ test:
99
./gradlew test
1010

1111
TEMP_TEST_OUTPUT=/tmp/sdk-test-service.log
12+
TEST_SERVICE_PORT ?= 8000
13+
SUPPRESSION_FILE=contract-tests/test-suppressions.txt
14+
SUPPRESSION_FILE_FDV2=contract-tests/test-suppressions-fdv2.txt
1215

1316
# Add any extra sdk-test-harness parameters here, such as -skip for tests that are
1417
# temporarily not working.
@@ -18,15 +21,19 @@ build-contract-tests:
1821
@cd contract-tests && ../gradlew installDist
1922

2023
start-contract-test-service:
21-
@contract-tests/service/build/install/service/bin/service
24+
@PORT=$(TEST_SERVICE_PORT) contract-tests/service/build/install/service/bin/service
2225

2326
start-contract-test-service-bg:
2427
@echo "Test service output will be captured in $(TEMP_TEST_OUTPUT)"
2528
@make start-contract-test-service >$(TEMP_TEST_OUTPUT) 2>&1 &
2629

2730
run-contract-tests:
31+
@echo "Running SDK contract test v2..."
2832
@curl -s https://raw.githubusercontent.com/launchdarkly/sdk-test-harness/v2/downloader/run.sh \
29-
| VERSION=v2 PARAMS="-url http://localhost:8000 -debug -stop-service-at-end $(TEST_HARNESS_PARAMS)" sh
33+
| VERSION=v2 PARAMS="-url http://localhost:$(TEST_SERVICE_PORT) -debug -skip-from=$(SUPPRESSION_FILE) $(TEST_HARNESS_PARAMS)" sh
34+
@echo "Running SDK contract test v3.0.0-alpha.1..."
35+
@curl -s https://raw.githubusercontent.com/launchdarkly/sdk-test-harness/v3.0.0-alpha.1/downloader/run.sh \
36+
| VERSION=v3.0.0-alpha.1 PARAMS="-url http://localhost:$(TEST_SERVICE_PORT) -debug -stop-service-at-end -skip-from=$(SUPPRESSION_FILE_FDV2) $(TEST_HARNESS_PARAMS)" sh
3037

3138
contract-tests: build-contract-tests start-contract-test-service-bg run-contract-tests
3239

lib/sdk/server/build.gradle

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -71,10 +71,10 @@ ext.versions = [
7171
"guava": "32.0.1-jre",
7272
"jackson": "2.11.2",
7373
"launchdarklyJavaSdkCommon": "2.1.2",
74-
"launchdarklyJavaSdkInternal": "1.5.1",
74+
"launchdarklyJavaSdkInternal": "1.6.1",
7575
"launchdarklyLogging": "1.1.0",
7676
"okhttp": "4.12.0", // specify this for the SDK build instead of relying on the transitive dependency from okhttp-eventsource
77-
"okhttpEventsource": "4.1.0",
77+
"okhttpEventsource": "4.2.0",
7878
"reactorCore":"3.3.22.RELEASE",
7979
"slf4j": "1.7.36",
8080
"snakeyaml": "2.4",

lib/sdk/server/contract-tests/service/src/main/java/sdktest/Representations.java

Lines changed: 108 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -32,6 +32,7 @@ public static class SdkConfigParams {
3232
SdkConfigTagParams tags;
3333
SdkConfigServiceEndpointParams serviceEndpoints;
3434
SdkConfigHookParams hooks;
35+
SdkConfigDataSystemParams dataSystem;
3536
}
3637

3738
public static class SdkConfigStreamParams {
@@ -73,6 +74,113 @@ public static class SdkConfigHookParams {
7374
List<HookConfig> hooks;
7475
}
7576

77+
/**
78+
* Constants for store mode values.
79+
*/
80+
public static class StoreMode {
81+
/**
82+
* Read-only mode - the data system will only read from the persistent store.
83+
*/
84+
public static final int READ = 0;
85+
86+
/**
87+
* Read-write mode - the data system can read from, and write to, the persistent store.
88+
*/
89+
public static final int READ_WRITE = 1;
90+
}
91+
92+
/**
93+
* Constants for persistent store type values.
94+
*/
95+
public static class PersistentStoreType {
96+
/**
97+
* Redis persistent store type.
98+
*/
99+
public static final String REDIS = "redis";
100+
101+
/**
102+
* DynamoDB persistent store type.
103+
*/
104+
public static final String DYNAMODB = "dynamodb";
105+
106+
/**
107+
* Consul persistent store type.
108+
*/
109+
public static final String CONSUL = "consul";
110+
}
111+
112+
/**
113+
* Constants for persistent cache mode values.
114+
*/
115+
public static class PersistentCacheMode {
116+
/**
117+
* Cache disabled mode.
118+
*/
119+
public static final String OFF = "off";
120+
121+
/**
122+
* Time-to-live cache mode with a specified TTL.
123+
*/
124+
public static final String TTL = "ttl";
125+
126+
/**
127+
* Infinite cache mode - cache forever.
128+
*/
129+
public static final String INFINITE = "infinite";
130+
}
131+
132+
public static class SdkConfigDataSystemParams {
133+
SdkConfigDataStoreParams store;
134+
Integer storeMode;
135+
SdkConfigDataInitializerParams[] initializers;
136+
SdkConfigSynchronizersParams synchronizers;
137+
String payloadFilter;
138+
}
139+
140+
public static class SdkConfigDataStoreParams {
141+
SdkConfigPersistentDataStoreParams persistentDataStore;
142+
}
143+
144+
public static class SdkConfigPersistentDataStoreParams {
145+
SdkConfigPersistentStoreParams store;
146+
SdkConfigPersistentCacheParams cache;
147+
}
148+
149+
public static class SdkConfigPersistentStoreParams {
150+
String type;
151+
String prefix;
152+
String dsn;
153+
}
154+
155+
public static class SdkConfigPersistentCacheParams {
156+
String mode;
157+
Integer ttl;
158+
}
159+
160+
public static class SdkConfigDataInitializerParams {
161+
SdkConfigPollingParams polling;
162+
}
163+
164+
public static class SdkConfigSynchronizersParams {
165+
SdkConfigSynchronizerParams primary;
166+
SdkConfigSynchronizerParams secondary;
167+
}
168+
169+
public static class SdkConfigSynchronizerParams {
170+
SdkConfigStreamingParams streaming;
171+
SdkConfigPollingParams polling;
172+
}
173+
174+
public static class SdkConfigPollingParams {
175+
URI baseUri;
176+
Long pollIntervalMs;
177+
}
178+
179+
public static class SdkConfigStreamingParams {
180+
URI baseUri;
181+
Long initialRetryDelayMs;
182+
}
183+
76184
public static class HookConfig {
77185
String name;
78186
URI callbackUri;

lib/sdk/server/contract-tests/service/src/main/java/sdktest/SdkClientEntity.java

Lines changed: 134 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -25,7 +25,16 @@
2525
import com.launchdarkly.sdk.server.integrations.HooksConfigurationBuilder;
2626
import com.launchdarkly.sdk.server.integrations.ServiceEndpointsBuilder;
2727
import com.launchdarkly.sdk.server.integrations.StreamingDataSourceBuilder;
28+
import com.launchdarkly.sdk.server.integrations.DataSystemBuilder;
29+
import com.launchdarkly.sdk.server.DataSystemComponents;
30+
import com.launchdarkly.sdk.server.integrations.FDv2PollingInitializerBuilder;
31+
import com.launchdarkly.sdk.server.integrations.FDv2PollingSynchronizerBuilder;
32+
import com.launchdarkly.sdk.server.integrations.FDv2StreamingSynchronizerBuilder;
2833
import com.launchdarkly.sdk.server.interfaces.BigSegmentStoreStatusProvider;
34+
import com.launchdarkly.sdk.server.subsystems.DataSourceBuilder;
35+
import com.launchdarkly.sdk.server.datasources.Initializer;
36+
import com.launchdarkly.sdk.server.datasources.Synchronizer;
37+
import com.launchdarkly.sdk.server.subsystems.DataSystemConfiguration;
2938

3039
import org.jetbrains.annotations.NotNull;
3140
import org.slf4j.Logger;
@@ -55,6 +64,12 @@
5564
import sdktest.Representations.HookConfig;
5665
import sdktest.Representations.SdkConfigHookParams;
5766
import sdktest.Representations.SdkConfigParams;
67+
import sdktest.Representations.SdkConfigDataSystemParams;
68+
import sdktest.Representations.SdkConfigDataInitializerParams;
69+
import sdktest.Representations.SdkConfigSynchronizersParams;
70+
import sdktest.Representations.SdkConfigSynchronizerParams;
71+
import sdktest.Representations.SdkConfigPollingParams;
72+
import sdktest.Representations.SdkConfigStreamingParams;
5873
import sdktest.Representations.SecureModeHashParams;
5974
import sdktest.Representations.SecureModeHashResponse;
6075

@@ -465,6 +480,125 @@ private LDConfig buildSdkConfig(SdkConfigParams params, String tag) {
465480
builder.hooks(Components.hooks().setHooks(hookList));
466481
}
467482

483+
if (params.dataSystem != null) {
484+
DataSystemBuilder dataSystemBuilder = Components.dataSystem().custom();
485+
486+
// TODO: enable this code in the future and determine which dependencies on persistent stores need to be added to contract test build process
487+
// Configure persistent store if provided
488+
// if (params.dataSystem.store != null && params.dataSystem.store.persistentDataStore != null) {
489+
// var storeConfig = params.dataSystem.store.persistentDataStore;
490+
// var storeType = storeConfig.store.type.toLowerCase();
491+
// ComponentConfigurer<DataStore> persistentStore = null;
492+
//
493+
// switch (storeType) {
494+
// case "redis":
495+
// // Redis store configuration
496+
// break;
497+
// case "dynamodb":
498+
// // DynamoDB store configuration
499+
// break;
500+
// case "consul":
501+
// // Consul store configuration
502+
// break;
503+
// }
504+
//
505+
// if (persistentStore != null) {
506+
// // Configure cache
507+
// var cacheMode = storeConfig.cache != null ? storeConfig.cache.mode.toLowerCase() : null;
508+
// // ... cache configuration ...
509+
//
510+
// // Determine store mode
511+
// var storeMode = params.dataSystem.storeMode == 0
512+
// ? DataSystemConfiguration.DataStoreMode.READ_ONLY
513+
// : DataSystemConfiguration.DataStoreMode.READ_WRITE;
514+
//
515+
// dataSystemBuilder.persistentStore(persistentStore, storeMode);
516+
// }
517+
// }
518+
519+
// Configure initializers
520+
if (params.dataSystem.initializers != null && params.dataSystem.initializers.length > 0) {
521+
List<DataSourceBuilder<Initializer>> initializers = new ArrayList<>();
522+
for (SdkConfigDataInitializerParams initializer : params.dataSystem.initializers) {
523+
if (initializer.polling != null) {
524+
FDv2PollingInitializerBuilder pollingBuilder = DataSystemComponents.pollingInitializer();
525+
if (initializer.polling.baseUri != null) {
526+
ServiceEndpointsBuilder endpointOverride = Components.serviceEndpoints().polling(initializer.polling.baseUri);
527+
pollingBuilder.serviceEndpointsOverride(endpointOverride);
528+
}
529+
// Note: pollInterval is not available for initializers, only for synchronizers
530+
if (params.dataSystem.payloadFilter != null && !params.dataSystem.payloadFilter.isEmpty()) {
531+
pollingBuilder.payloadFilter(params.dataSystem.payloadFilter);
532+
}
533+
initializers.add(pollingBuilder);
534+
}
535+
}
536+
if (!initializers.isEmpty()) {
537+
dataSystemBuilder.initializers(initializers.toArray(new DataSourceBuilder[0]));
538+
}
539+
}
540+
541+
// Configure synchronizers
542+
if (params.dataSystem.synchronizers != null) {
543+
List<DataSourceBuilder<Synchronizer>> synchronizers = new ArrayList<>();
544+
545+
// Primary synchronizer
546+
if (params.dataSystem.synchronizers.primary != null) {
547+
DataSourceBuilder<Synchronizer> primary = createSynchronizer(params.dataSystem.synchronizers.primary, params.dataSystem.payloadFilter);
548+
if (primary != null) {
549+
synchronizers.add(primary);
550+
}
551+
}
552+
553+
// Secondary synchronizer (optional)
554+
if (params.dataSystem.synchronizers.secondary != null) {
555+
DataSourceBuilder<Synchronizer> secondary = createSynchronizer(params.dataSystem.synchronizers.secondary, params.dataSystem.payloadFilter);
556+
if (secondary != null) {
557+
synchronizers.add(secondary);
558+
}
559+
}
560+
561+
if (!synchronizers.isEmpty()) {
562+
dataSystemBuilder.synchronizers(synchronizers.toArray(new DataSourceBuilder[0]));
563+
}
564+
}
565+
566+
builder.dataSystem(dataSystemBuilder);
567+
}
568+
468569
return builder.build();
469570
}
571+
572+
private DataSourceBuilder<Synchronizer> createSynchronizer(
573+
SdkConfigSynchronizerParams synchronizer,
574+
String payloadFilter) {
575+
if (synchronizer.polling != null) {
576+
FDv2PollingSynchronizerBuilder pollingBuilder = DataSystemComponents.pollingSynchronizer();
577+
if (synchronizer.polling.baseUri != null) {
578+
ServiceEndpointsBuilder endpointOverride = Components.serviceEndpoints().polling(synchronizer.polling.baseUri);
579+
pollingBuilder.serviceEndpointsOverride(endpointOverride);
580+
}
581+
if (synchronizer.polling.pollIntervalMs != null) {
582+
pollingBuilder.pollInterval(Duration.ofMillis(synchronizer.polling.pollIntervalMs));
583+
}
584+
if (payloadFilter != null && !payloadFilter.isEmpty()) {
585+
pollingBuilder.payloadFilter(payloadFilter);
586+
}
587+
return pollingBuilder;
588+
} else if (synchronizer.streaming != null) {
589+
FDv2StreamingSynchronizerBuilder streamingBuilder = DataSystemComponents.streamingSynchronizer();
590+
if (synchronizer.streaming.baseUri != null) {
591+
ServiceEndpointsBuilder endpointOverride = Components.serviceEndpoints().streaming(synchronizer.streaming.baseUri);
592+
streamingBuilder.serviceEndpointsOverride(endpointOverride);
593+
}
594+
if (synchronizer.streaming.initialRetryDelayMs != null) {
595+
streamingBuilder.initialReconnectDelay(Duration.ofMillis(synchronizer.streaming.initialRetryDelayMs));
596+
}
597+
if (payloadFilter != null && !payloadFilter.isEmpty()) {
598+
streamingBuilder.payloadFilter(payloadFilter);
599+
}
600+
return streamingBuilder;
601+
}
602+
return null;
603+
}
470604
}
Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,4 @@
1+
streaming/validation/unrecognized data that can be safely ignored/unknown event name with JSON body
2+
streaming/validation/unrecognized data that can be safely ignored/unknown event name with non-JSON body
3+
streaming/validation/unrecognized data that can be safely ignored/patch event with unrecognized path kind
4+
streaming/fdv2/fallback to FDv1 handling

lib/sdk/server/contract-tests/test-suppressions.txt

Whitespace-only changes.
Lines changed: 31 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,31 @@
1+
package com.launchdarkly.sdk.server;
2+
3+
import com.launchdarkly.sdk.server.subsystems.DataStoreTypes.FullDataSet;
4+
import com.launchdarkly.sdk.server.subsystems.DataStoreTypes.ItemDescriptor;
5+
6+
/**
7+
* Optional interface for data stores that can export their entire contents.
8+
* <p>
9+
* This interface is used to enable recovery scenarios where a persistent store
10+
* needs to be re-synchronized from an in-memory cache. Not all data stores need
11+
* to implement this interface.
12+
* <p>
13+
* This is currently only for internal implementations.
14+
*/
15+
interface CacheExporter {
16+
/**
17+
* Exports all data from the cache across all known DataKinds.
18+
*
19+
* @return A FullDataSet containing all items in the cache. The data is a snapshot
20+
* taken at the time of the call and may be stale immediately after return.
21+
*/
22+
FullDataSet<ItemDescriptor> exportAll();
23+
24+
/**
25+
* Indicates if the cache has been populated with a full data set.
26+
*
27+
* @return true when the cache has been populated
28+
*/
29+
boolean isInitialized();
30+
}
31+

lib/sdk/server/src/main/java/com/launchdarkly/sdk/server/Components.java

Lines changed: 18 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -26,6 +26,7 @@
2626
import com.launchdarkly.sdk.server.integrations.PollingDataSourceBuilder;
2727
import com.launchdarkly.sdk.server.integrations.ServiceEndpointsBuilder;
2828
import com.launchdarkly.sdk.server.integrations.StreamingDataSourceBuilder;
29+
import com.launchdarkly.sdk.server.integrations.DataSystemModes;
2930
import com.launchdarkly.sdk.server.integrations.WrapperInfoBuilder;
3031
import com.launchdarkly.sdk.server.interfaces.HttpAuthentication;
3132
import com.launchdarkly.sdk.server.subsystems.BigSegmentStore;
@@ -476,4 +477,21 @@ public static PluginsConfigurationBuilder plugins() {
476477
* @since 7.1.0
477478
*/
478479
public static WrapperInfoBuilder wrapperInfo() { return new WrapperInfoBuilderImpl(); }
480+
481+
/**
482+
* This API is under active development. Do not use.
483+
*
484+
* Returns a set of builder options for configuring the SDK data system. When the data system configuration
485+
* is used it overrides {@link LDConfig.Builder#dataSource(ComponentConfigurer)} and
486+
* {@link LDConfig.Builder#dataStore(ComponentConfigurer)} in the configuration.
487+
* <p>
488+
* This class is not stable, and not subject to any backwards compatibility guarantees or semantic versioning.
489+
* It is in early access. If you want access to this feature please join the EAP. https://launchdarkly.com/docs/sdk/features/data-saving-mode
490+
* </p>
491+
*
492+
* @return a configuration builder
493+
*/
494+
public static DataSystemModes dataSystem() {
495+
return new DataSystemModes();
496+
}
479497
}

0 commit comments

Comments
 (0)