Skip to content
This repository was archived by the owner on May 30, 2024. It is now read-only.

Commit 37c9247

Browse files
LaunchDarklyReleaseBoteli-darklyLaunchDarklyReleaseBotaengelbergantonmos
authored
prepare 7.4.0 release (#326)
## [7.4.0] - 2024-04-26 ### Added: - This release introduces a Hooks API. Hooks are collections of user-defined callbacks that are executed by the SDK at various points of interest. You can use them to augment the SDK with metrics or tracing. --------- Co-authored-by: Eli Bishop <[email protected]> Co-authored-by: LaunchDarklyReleaseBot <[email protected]> Co-authored-by: Alex Engelberg <[email protected]> Co-authored-by: Anton Mostovoy <[email protected]> Co-authored-by: LaunchDarklyCI <[email protected]> Co-authored-by: LaunchDarklyCI <[email protected]> Co-authored-by: Gavin Whelan <[email protected]> Co-authored-by: ssrm <[email protected]> Co-authored-by: Harpo Roeder <[email protected]> Co-authored-by: Ben Woskow <[email protected]> Co-authored-by: Elliot <[email protected]> Co-authored-by: Robert J. Neal <[email protected]> Co-authored-by: Robert J. Neal <[email protected]> Co-authored-by: Sam Stokes <[email protected]> Co-authored-by: Ember Stevens <[email protected]> Co-authored-by: ember-stevens <[email protected]> Co-authored-by: Alex Engelberg <[email protected]> Co-authored-by: Louis Chan <[email protected]> Co-authored-by: Louis Chan <[email protected]> Co-authored-by: Todd Anderson <[email protected]> Co-authored-by: tanderson-ld <[email protected]> Co-authored-by: Matthew M. Keeler <[email protected]> Co-authored-by: ld-repository-standards[bot] <113625520+ld-repository-standards[bot]@users.noreply.github.com> Co-authored-by: Kane Parkinson <[email protected]> Co-authored-by: Ryan Lamb <[email protected]>
1 parent 42319e7 commit 37c9247

34 files changed

+1487
-416
lines changed

CODEOWNERS

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,2 +1,2 @@
11
# Repository Maintainers
2-
* @launchdarkly/team-sdk
2+
* @launchdarkly/team-sdk-java

build.gradle

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -151,6 +151,7 @@ libraries.optional = [
151151
libraries.test = [
152152
"org.hamcrest:hamcrest-all:1.3",
153153
"org.easymock:easymock:3.4",
154+
"org.mockito:mockito-core:3.+",
154155
"junit:junit:4.12",
155156
"com.fasterxml.jackson.core:jackson-core:${versions.jackson}",
156157
"com.fasterxml.jackson.core:jackson-databind:${versions.jackson}",
Lines changed: 42 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,42 @@
1+
package sdktest;
2+
3+
import okhttp3.MediaType;
4+
import okhttp3.Request;
5+
import okhttp3.RequestBody;
6+
import okhttp3.Response;
7+
8+
import java.net.URI;
9+
10+
public class HookCallbackService {
11+
private final URI serviceUri;
12+
13+
public HookCallbackService(URI serviceUri) {
14+
this.serviceUri = serviceUri;
15+
}
16+
17+
public void post(Object params) {
18+
try {
19+
RequestBody body = RequestBody.create(
20+
TestService.gson.toJson(params == null ? "{}" : params),
21+
MediaType.parse("application/json"));
22+
Request request = new Request.Builder().url(serviceUri.toString()).
23+
method("POST", body).build();
24+
Response response = TestService.client.newCall(request).execute();
25+
assertOk(response);
26+
} catch (Exception e) {
27+
throw new RuntimeException(e); // all errors are unexpected here
28+
}
29+
}
30+
31+
private void assertOk(Response response) {
32+
if (!response.isSuccessful()) {
33+
String body = "";
34+
if (response.body() != null) {
35+
try {
36+
body = ": " + response.body().string();
37+
} catch (Exception e) {}
38+
}
39+
throw new RuntimeException("HTTP error " + response.code() + " from callback to " + serviceUri + body);
40+
}
41+
}
42+
}

contract-tests/service/src/main/java/sdktest/Representations.java

Lines changed: 40 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -2,10 +2,12 @@
22

33
import com.google.gson.annotations.SerializedName;
44
import com.launchdarkly.sdk.EvaluationReason;
5+
import com.launchdarkly.sdk.EvaluationDetail;
56
import com.launchdarkly.sdk.LDContext;
67
import com.launchdarkly.sdk.LDValue;
78

89
import java.net.URI;
10+
import java.util.List;
911
import java.util.Map;
1012

1113
public abstract class Representations {
@@ -29,6 +31,7 @@ public static class SdkConfigParams {
2931
SdkConfigBigSegmentsParams bigSegments;
3032
SdkConfigTagParams tags;
3133
SdkConfigServiceEndpointParams serviceEndpoints;
34+
SdkConfigHookParams hooks;
3235
}
3336

3437
public static class SdkConfigStreamParams {
@@ -64,7 +67,28 @@ public static class SdkConfigServiceEndpointParams {
6467
String polling;
6568
String events;
6669
}
67-
70+
71+
public static class SdkConfigHookParams {
72+
List<HookConfig> hooks;
73+
}
74+
75+
public static class HookConfig {
76+
String name;
77+
URI callbackUri;
78+
HookData data;
79+
HookErrors errors;
80+
}
81+
82+
public static class HookData {
83+
Map<String, Object> beforeEvaluation;
84+
Map<String, Object> afterEvaluation;
85+
}
86+
87+
public static class HookErrors {
88+
String beforeEvaluation;
89+
String afterEvaluation;
90+
}
91+
6892
public static class CommandParams {
6993
String command;
7094
EvaluateFlagParams evaluate;
@@ -105,7 +129,21 @@ public static class EvaluateAllFlagsParams {
105129
public static class EvaluateAllFlagsResponse {
106130
LDValue state;
107131
}
108-
132+
133+
public static class EvaluationHookCallbackParams {
134+
EvaluationSeriesContextParam evaluationSeriesContext;
135+
Map<String, Object> evaluationSeriesData;
136+
EvaluationDetail<LDValue> evaluationDetail;
137+
String stage;
138+
}
139+
140+
public static class EvaluationSeriesContextParam {
141+
String flagKey;
142+
LDContext context;
143+
LDValue defaultValue;
144+
String method;
145+
}
146+
109147
public static class IdentifyEventParams {
110148
LDContext context;
111149
}

contract-tests/service/src/main/java/sdktest/SdkClientEntity.java

Lines changed: 29 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -18,9 +18,11 @@
1818
import com.launchdarkly.sdk.server.migrations.MigrationSerialOrder;
1919
import com.launchdarkly.sdk.server.MigrationStage;
2020
import com.launchdarkly.sdk.server.MigrationVariation;
21+
import com.launchdarkly.sdk.server.integrations.Hook;
2122
import com.launchdarkly.sdk.server.integrations.ApplicationInfoBuilder;
2223
import com.launchdarkly.sdk.server.integrations.BigSegmentsConfigurationBuilder;
2324
import com.launchdarkly.sdk.server.integrations.EventProcessorBuilder;
25+
import com.launchdarkly.sdk.server.integrations.HooksConfigurationBuilder;
2426
import com.launchdarkly.sdk.server.integrations.ServiceEndpointsBuilder;
2527
import com.launchdarkly.sdk.server.integrations.StreamingDataSourceBuilder;
2628
import com.launchdarkly.sdk.server.interfaces.BigSegmentStoreStatusProvider;
@@ -32,6 +34,7 @@
3234
import java.net.URL;
3335
import java.time.Duration;
3436
import java.util.ArrayList;
37+
import java.util.Collections;
3538
import java.util.List;
3639
import java.util.Map;
3740
import java.util.Optional;
@@ -49,6 +52,8 @@
4952
import sdktest.Representations.EvaluateFlagResponse;
5053
import sdktest.Representations.GetBigSegmentsStoreStatusResponse;
5154
import sdktest.Representations.IdentifyEventParams;
55+
import sdktest.Representations.HookConfig;
56+
import sdktest.Representations.SdkConfigHookParams;
5257
import sdktest.Representations.SdkConfigParams;
5358
import sdktest.Representations.SecureModeHashParams;
5459
import sdktest.Representations.SecureModeHashResponse;
@@ -216,7 +221,7 @@ private ContextBuildResponse doContextBuild(ContextBuildParams params) {
216221
c = doContextBuildSingle(params.single);
217222
} else {
218223
ContextMultiBuilder b = LDContext.multiBuilder();
219-
for (ContextBuildSingleParams s: params.multi) {
224+
for (ContextBuildSingleParams s : params.multi) {
220225
b.add(doContextBuildSingle(s));
221226
}
222227
c = b.build();
@@ -238,7 +243,7 @@ private LDContext doContextBuildSingle(ContextBuildSingleParams params) {
238243
b.anonymous(params.anonymous.booleanValue());
239244
}
240245
if (params.custom != null) {
241-
for (Map.Entry<String, LDValue> kv: params.custom.entrySet()) {
246+
for (Map.Entry<String, LDValue> kv : params.custom.entrySet()) {
242247
b.set(kv.getKey(), kv.getValue());
243248
}
244249
}
@@ -291,20 +296,20 @@ private Representations.MigrationOperationResponse doMigrationOperation(Represen
291296
(payload) -> getMigrationMethodResult(payload, newService));
292297
}
293298
Optional<Migration<String, String, String, String>> opt = migrationBuilder.build();
294-
if(!opt.isPresent()) {
299+
if (!opt.isPresent()) {
295300
return null;
296301
}
297302
Migration<String, String, String, String> migration = opt.get();
298303

299-
switch(params.operation) {
304+
switch (params.operation) {
300305
case "read": {
301306
Migration.MigrationResult<String> res = migration.read(
302307
params.key,
303308
params.context,
304309
MigrationStage.of(params.defaultStage, MigrationStage.OFF),
305310
params.payload);
306311
Representations.MigrationOperationResponse response = new Representations.MigrationOperationResponse();
307-
if(res.isSuccess()) {
312+
if (res.isSuccess()) {
308313
response.result = res.getResult().orElse(null);
309314
} else {
310315
response.error = res.getException().map(ex -> ex.getMessage()).orElse(null);
@@ -338,7 +343,7 @@ private static MigrationMethodResult<String> getMigrationMethodResult(String pay
338343
}
339344

340345
private MigrationExecution getExecution(String execution) {
341-
switch(execution) {
346+
switch (execution) {
342347
case "serial":
343348
return MigrationExecution.Serial(MigrationSerialOrder.FIXED);
344349
case "random":
@@ -441,6 +446,24 @@ private LDConfig buildSdkConfig(SdkConfigParams params, String tag) {
441446
}
442447
builder.serviceEndpoints(endpoints);
443448

449+
if (params.hooks != null && params.hooks.hooks != null) {
450+
List<Hook> hookList = new ArrayList<>();
451+
for (HookConfig hookConfig : params.hooks.hooks) {
452+
453+
HookCallbackService callbackService = new HookCallbackService(hookConfig.callbackUri);
454+
TestHook testHook = new TestHook(
455+
hookConfig.name,
456+
callbackService,
457+
hookConfig.data != null ? hookConfig.data.beforeEvaluation : Collections.emptyMap(),
458+
hookConfig.data != null ? hookConfig.data.afterEvaluation : Collections.emptyMap(),
459+
hookConfig.errors != null ? hookConfig.errors.beforeEvaluation : null,
460+
hookConfig.errors != null ? hookConfig.errors.afterEvaluation : null
461+
);
462+
hookList.add(testHook);
463+
}
464+
builder.hooks(Components.hooks().setHooks(hookList));
465+
}
466+
444467
return builder.build();
445468
}
446469
}
Lines changed: 89 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,89 @@
1+
package sdktest;
2+
3+
import com.launchdarkly.sdk.server.integrations.Hook;
4+
import com.launchdarkly.sdk.server.integrations.HookMetadata;
5+
import com.launchdarkly.sdk.server.integrations.EvaluationSeriesContext;
6+
import com.launchdarkly.sdk.EvaluationDetail;
7+
import com.launchdarkly.sdk.LDValue;
8+
9+
import sdktest.Representations.EvaluationHookCallbackParams;
10+
import sdktest.Representations.EvaluationSeriesContextParam;
11+
12+
import java.util.Collections;
13+
import java.util.Map;
14+
import java.util.HashMap;
15+
16+
class TestHook extends Hook {
17+
18+
private HookCallbackService callbackService;
19+
private Map<String, Object> beforeEvaluationData;
20+
private Map<String, Object> afterEvaluationData;
21+
private String beforeEvaluationError;
22+
private String afterEvaluationError;
23+
24+
TestHook(String name, HookCallbackService callbackService, Map<String, Object> beforeEvaluationData, Map<String, Object> afterEvaluationData, String beforeEvaluationError, String afterEvaluationError) {
25+
super(name);
26+
this.callbackService = callbackService;
27+
this.beforeEvaluationData = beforeEvaluationData;
28+
this.afterEvaluationData = afterEvaluationData;
29+
this.beforeEvaluationError = beforeEvaluationError;
30+
this.afterEvaluationError = afterEvaluationError;
31+
}
32+
33+
@Override
34+
public Map<String, Object> beforeEvaluation(EvaluationSeriesContext seriesContext, Map<String, Object> data) {
35+
36+
if (this.beforeEvaluationError != null) {
37+
throw new RuntimeException(this.beforeEvaluationError);
38+
}
39+
40+
EvaluationHookCallbackParams params = new EvaluationHookCallbackParams();
41+
42+
EvaluationSeriesContextParam seriesContextParam = new EvaluationSeriesContextParam();
43+
seriesContextParam.flagKey = seriesContext.flagKey;
44+
seriesContextParam.context = seriesContext.context;
45+
seriesContextParam.defaultValue = seriesContext.defaultValue;
46+
seriesContextParam.method = seriesContext.method;
47+
params.evaluationSeriesContext = seriesContextParam;
48+
49+
params.evaluationSeriesData = data;
50+
params.stage = "beforeEvaluation";
51+
callbackService.post(params);
52+
53+
HashMap<String, Object> newData = new HashMap<>(data);
54+
if (beforeEvaluationData != null) {
55+
newData.putAll(beforeEvaluationData);
56+
}
57+
58+
return Collections.unmodifiableMap(newData);
59+
}
60+
61+
@Override
62+
public Map<String, Object> afterEvaluation(EvaluationSeriesContext seriesContext, Map<String, Object> data, EvaluationDetail<LDValue> evaluationDetail) {
63+
64+
if (this.afterEvaluationError != null) {
65+
throw new RuntimeException(this.afterEvaluationError);
66+
}
67+
68+
EvaluationHookCallbackParams params = new EvaluationHookCallbackParams();
69+
70+
EvaluationSeriesContextParam seriesContextParam = new EvaluationSeriesContextParam();
71+
seriesContextParam.flagKey = seriesContext.flagKey;
72+
seriesContextParam.context = seriesContext.context;
73+
seriesContextParam.defaultValue = seriesContext.defaultValue;
74+
seriesContextParam.method = seriesContext.method;
75+
params.evaluationSeriesContext = seriesContextParam;
76+
77+
params.evaluationSeriesData = data;
78+
params.evaluationDetail = evaluationDetail;
79+
params.stage = "afterEvaluation";
80+
callbackService.post(params);
81+
82+
HashMap<String, Object> newData = new HashMap<>(data);
83+
if (afterEvaluationData != null) {
84+
newData.putAll(afterEvaluationData);
85+
}
86+
87+
return Collections.unmodifiableMap(newData);
88+
}
89+
}

contract-tests/service/src/main/java/sdktest/TestService.java

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -36,7 +36,8 @@ public class TestService {
3636
"migrations",
3737
"event-sampling",
3838
"inline-context",
39-
"anonymous-redaction"
39+
"anonymous-redaction",
40+
"evaluation-hooks"
4041
};
4142

4243
static final Gson gson = new GsonBuilder().serializeNulls().create();

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

Lines changed: 24 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -3,6 +3,7 @@
33
import com.launchdarkly.logging.LDLogAdapter;
44
import com.launchdarkly.logging.Logs;
55
import com.launchdarkly.sdk.server.ComponentsImpl.EventProcessorBuilderImpl;
6+
import com.launchdarkly.sdk.server.ComponentsImpl.HooksConfigurationBuilderImpl;
67
import com.launchdarkly.sdk.server.ComponentsImpl.HttpBasicAuthentication;
78
import com.launchdarkly.sdk.server.ComponentsImpl.HttpConfigurationBuilderImpl;
89
import com.launchdarkly.sdk.server.ComponentsImpl.InMemoryDataStoreFactory;
@@ -16,6 +17,7 @@
1617
import com.launchdarkly.sdk.server.integrations.ApplicationInfoBuilder;
1718
import com.launchdarkly.sdk.server.integrations.BigSegmentsConfigurationBuilder;
1819
import com.launchdarkly.sdk.server.integrations.EventProcessorBuilder;
20+
import com.launchdarkly.sdk.server.integrations.HooksConfigurationBuilder;
1921
import com.launchdarkly.sdk.server.integrations.HttpConfigurationBuilder;
2022
import com.launchdarkly.sdk.server.integrations.LoggingConfigurationBuilder;
2123
import com.launchdarkly.sdk.server.integrations.PersistentDataStoreBuilder;
@@ -31,7 +33,7 @@
3133
import com.launchdarkly.sdk.server.subsystems.EventProcessor;
3234
import com.launchdarkly.sdk.server.subsystems.PersistentDataStore;
3335

34-
import static com.launchdarkly.sdk.server.ComponentsImpl.NULL_EVENT_PROCESSOR_FACTORY;
36+
import static com.launchdarkly.sdk.server.ComponentsImpl.NOOP_EVENT_PROCESSOR_FACTORY;
3537

3638
/**
3739
* Provides configurable factories for the standard implementations of LaunchDarkly component interfaces.
@@ -172,7 +174,7 @@ public static EventProcessorBuilder sendEvents() {
172174
* @since 4.12.0
173175
*/
174176
public static ComponentConfigurer<EventProcessor> noEvents() {
175-
return NULL_EVENT_PROCESSOR_FACTORY;
177+
return NOOP_EVENT_PROCESSOR_FACTORY;
176178
}
177179

178180
/**
@@ -423,6 +425,26 @@ public static ServiceEndpointsBuilder serviceEndpoints() {
423425
return new ServiceEndpointsBuilderImpl();
424426
}
425427

428+
/**
429+
* Returns a builder for configuring hooks.
430+
*
431+
* Passing this to {@link LDConfig.Builder#hooks(com.launchdarkly.sdk.server.integrations.HooksConfigurationBuilder)},
432+
* after setting any desired hooks on the builder, applies this configuration to the SDK.
433+
* <pre><code>
434+
* List hooks = myCreateHooksFunc();
435+
* LDConfig config = new LDConfig.Builder()
436+
* .hooks(
437+
* Components.hooks()
438+
* .setHooks(hooks)
439+
* )
440+
* .build();
441+
* </code></pre>
442+
* @return a {@link HooksConfigurationBuilder} that can be used for customization
443+
*/
444+
public static HooksConfigurationBuilder hooks() {
445+
return new HooksConfigurationBuilderImpl();
446+
}
447+
426448
/**
427449
* Returns a wrapper information builder.
428450
* <p>

0 commit comments

Comments
 (0)