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

Commit 6ad64db

Browse files
prepare 5.4.0 release (#233)
1 parent e0bb18a commit 6ad64db

14 files changed

+311
-21
lines changed

.circleci/config.yml

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -144,7 +144,7 @@ jobs:
144144
- run: cat gradle.properties.example >>gradle.properties
145145
- run:
146146
name: checkstyle/javadoc
147-
command: ./gradlew checkstyleMain
147+
command: ./gradlew javadoc checkstyleMain
148148
- run:
149149
name: build all SDK jars
150150
command: ./gradlew publishToMavenLocal -P LD_SKIP_SIGNING=1

build.gradle

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -72,7 +72,7 @@ ext.versions = [
7272
"gson": "2.7",
7373
"guava": "30.1-jre",
7474
"jackson": "2.11.2",
75-
"launchdarklyJavaSdkCommon": "1.0.0",
75+
"launchdarklyJavaSdkCommon": "1.1.1",
7676
"okhttp": "4.8.1", // specify this for the SDK build instead of relying on the transitive dependency from okhttp-eventsource
7777
"okhttpEventsource": "2.3.1",
7878
"slf4j": "1.7.21",

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

Lines changed: 13 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -36,7 +36,9 @@ abstract Event.FeatureRequest newUnknownFeatureRequestEvent(
3636
abstract Event.Custom newCustomEvent(String key, LDUser user, LDValue data, Double metricValue);
3737

3838
abstract Event.Identify newIdentifyEvent(LDUser user);
39-
39+
40+
abstract Event.AliasEvent newAliasEvent(LDUser user, LDUser previousUser);
41+
4042
final Event.FeatureRequest newFeatureRequestEvent(
4143
DataModel.FeatureFlag flag,
4244
LDUser user,
@@ -166,6 +168,11 @@ Event.Custom newCustomEvent(String key, LDUser user, LDValue data, Double metric
166168
Event.Identify newIdentifyEvent(LDUser user) {
167169
return new Event.Identify(timestampFn.get(), user);
168170
}
171+
172+
@Override
173+
Event.AliasEvent newAliasEvent(LDUser user, LDUser previousUser) {
174+
return new Event.AliasEvent(timestampFn.get(), user, previousUser);
175+
}
169176
}
170177

171178
static final class Disabled extends EventFactory {
@@ -191,6 +198,11 @@ final Custom newCustomEvent(String key, LDUser user, LDValue data, Double metric
191198
final Identify newIdentifyEvent(LDUser user) {
192199
return null;
193200
}
201+
202+
@Override
203+
Event.AliasEvent newAliasEvent(LDUser user, LDUser previousUser) {
204+
return null;
205+
}
194206
}
195207

196208
private static boolean isExperiment(DataModel.FeatureFlag flag, EvaluationReason reason) {

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

Lines changed: 16 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -64,26 +64,38 @@ private final void writeOutputEvent(Event event, JsonWriter jw) throws IOExcepti
6464
jw.value(fe.getPrereqOf());
6565
}
6666
writeEvaluationReason("reason", fe.getReason(), jw);
67-
jw.endObject();
67+
if (!fe.getContextKind().equals("user")) {
68+
jw.name("contextKind").value(fe.getContextKind());
69+
}
6870
} else if (event instanceof Event.Identify) {
6971
startEvent(event, "identify", event.getUser() == null ? null : event.getUser().getKey(), jw);
7072
writeUser(event.getUser(), jw);
71-
jw.endObject();
7273
} else if (event instanceof Event.Custom) {
7374
Event.Custom ce = (Event.Custom)event;
7475
startEvent(event, "custom", ce.getKey(), jw);
7576
writeUserOrKey(ce, false, jw);
7677
writeLDValue("data", ce.getData(), jw);
78+
if (!ce.getContextKind().equals("user")) {
79+
jw.name("contextKind").value(ce.getContextKind());
80+
}
7781
if (ce.getMetricValue() != null) {
7882
jw.name("metricValue");
7983
jw.value(ce.getMetricValue());
8084
}
81-
jw.endObject();
8285
} else if (event instanceof Event.Index) {
8386
startEvent(event, "index", null, jw);
8487
writeUser(event.getUser(), jw);
85-
jw.endObject();
88+
} else if (event instanceof Event.AliasEvent) {
89+
Event.AliasEvent ae = (Event.AliasEvent)event;
90+
startEvent(event, "alias", ae.getKey(), jw);
91+
jw.name("contextKind").value(ae.getContextKind());
92+
jw.name("previousKey").value(ae.getPreviousKey());
93+
jw.name("previousContextKind").value(ae.getPreviousContextKind());
94+
} else {
95+
return;
8696
}
97+
98+
jw.endObject();
8799
}
88100

89101
private final void writeSummaryEvent(EventSummarizer.EventSummary summary, JsonWriter jw) throws IOException {

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

Lines changed: 35 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -180,14 +180,48 @@ static class JsonSerialization extends TypeAdapter<FeatureFlagsState> {
180180
@Override
181181
public void write(JsonWriter out, FeatureFlagsState state) throws IOException {
182182
out.beginObject();
183+
183184
for (Map.Entry<String, LDValue> entry: state.flagValues.entrySet()) {
184185
out.name(entry.getKey());
185186
gsonInstance().toJson(entry.getValue(), LDValue.class, out);
186187
}
188+
187189
out.name("$flagsState");
188-
gsonInstance().toJson(state.flagMetadata, Map.class, out);
190+
out.beginObject();
191+
for (Map.Entry<String, FlagMetadata> entry: state.flagMetadata.entrySet()) {
192+
out.name(entry.getKey());
193+
FlagMetadata meta = entry.getValue();
194+
out.beginObject();
195+
// Here we're serializing FlagMetadata properties individually because if we rely on
196+
// Gson's reflection mechanism, it won't reliably drop null properties (that only works
197+
// if the destination really is Gson, not if a Jackson adapter is being used).
198+
if (meta.variation != null) {
199+
out.name("variation");
200+
out.value(meta.variation.intValue());
201+
}
202+
if (meta.reason != null) {
203+
out.name("reason");
204+
gsonInstance().toJson(meta.reason, EvaluationReason.class, out);
205+
}
206+
if (meta.version != null) {
207+
out.name("version");
208+
out.value(meta.version.intValue());
209+
}
210+
if (meta.trackEvents != null) {
211+
out.name("trackEvents");
212+
out.value(meta.trackEvents.booleanValue());
213+
}
214+
if (meta.debugEventsUntilDate != null) {
215+
out.name("debugEventsUntilDate");
216+
out.value(meta.debugEventsUntilDate.longValue());
217+
}
218+
out.endObject();
219+
}
220+
out.endObject();
221+
189222
out.name("$valid");
190223
out.value(state.valid);
224+
191225
out.endObject();
192226
}
193227

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

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -526,6 +526,11 @@ public String secureModeHash(LDUser user) {
526526
return null;
527527
}
528528

529+
@Override
530+
public void alias(LDUser user, LDUser previousUser) {
531+
this.eventProcessor.sendEvent(eventFactoryDefault.newAliasEvent(user, previousUser));
532+
}
533+
529534
/**
530535
* Returns the current version string of the client library.
531536
* @return a version string conforming to Semantic Versioning (http://semver.org)

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

Lines changed: 11 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -5,6 +5,7 @@
55

66
import org.slf4j.Logger;
77

8+
import java.io.Closeable;
89
import java.util.concurrent.Callable;
910
import java.util.concurrent.ScheduledExecutorService;
1011
import java.util.concurrent.ScheduledFuture;
@@ -17,7 +18,7 @@
1718
* This is currently only used by PersistentDataStoreWrapper, but encapsulating it in its own class helps with
1819
* clarity and also lets us reuse this logic in tests.
1920
*/
20-
final class PersistentDataStoreStatusManager {
21+
final class PersistentDataStoreStatusManager implements Closeable {
2122
private static final Logger logger = Loggers.DATA_STORE;
2223
static final int POLL_INTERVAL_MS = 500; // visible for testing
2324

@@ -42,6 +43,15 @@ final class PersistentDataStoreStatusManager {
4243
this.scheduler = sharedExecutor;
4344
}
4445

46+
public void close() {
47+
synchronized (this) {
48+
if (pollerFuture != null) {
49+
pollerFuture.cancel(true);
50+
pollerFuture = null;
51+
}
52+
}
53+
}
54+
4555
void updateAvailability(boolean available) {
4656
synchronized (this) {
4757
if (lastAvailable == available) {

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

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -142,6 +142,7 @@ private static CacheBuilder<Object, Object> newCacheBuilder(
142142

143143
@Override
144144
public void close() throws IOException {
145+
statusManager.close();
145146
core.close();
146147
}
147148

src/main/java/com/launchdarkly/sdk/server/interfaces/Event.java

Lines changed: 87 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -40,6 +40,15 @@ public long getCreationDate() {
4040
public LDUser getUser() {
4141
return user;
4242
}
43+
44+
/**
45+
* Convert a user into a context kind string
46+
* @param user the user to get the context kind from
47+
* @return the context kind string
48+
*/
49+
private static final String computeContextKind(LDUser user) {
50+
return user != null && user.isAnonymous() ? "anonymousUser" : "user";
51+
}
4352

4453
/**
4554
* A custom event created with {@link LDClientInterface#track(String, LDUser)} or one of its overloads.
@@ -48,6 +57,7 @@ public static final class Custom extends Event {
4857
private final String key;
4958
private final LDValue data;
5059
private final Double metricValue;
60+
private final String contextKind;
5161

5262
/**
5363
* Constructs a custom event.
@@ -61,8 +71,9 @@ public static final class Custom extends Event {
6171
public Custom(long timestamp, String key, LDUser user, LDValue data, Double metricValue) {
6272
super(timestamp, user);
6373
this.key = key;
64-
this.data = data == null ? LDValue.ofNull() : data;
74+
this.data = LDValue.normalize(data);
6575
this.metricValue = metricValue;
76+
this.contextKind = computeContextKind(user);
6677
}
6778

6879
/**
@@ -88,6 +99,14 @@ public LDValue getData() {
8899
public Double getMetricValue() {
89100
return metricValue;
90101
}
102+
103+
/**
104+
* The context kind of the user that generated this event
105+
* @return the context kind
106+
*/
107+
public String getContextKind() {
108+
return contextKind;
109+
}
91110
}
92111

93112
/**
@@ -132,6 +151,7 @@ public static final class FeatureRequest extends Event {
132151
private final long debugEventsUntilDate;
133152
private final EvaluationReason reason;
134153
private final boolean debug;
154+
private final String contextKind;
135155

136156
/**
137157
* Constructs a feature request event.
@@ -162,6 +182,7 @@ public FeatureRequest(long timestamp, String key, LDUser user, int version, int
162182
this.debugEventsUntilDate = debugEventsUntilDate;
163183
this.reason = reason;
164184
this.debug = debug;
185+
this.contextKind = computeContextKind(user);
165186
}
166187

167188
/**
@@ -243,5 +264,70 @@ public EvaluationReason getReason() {
243264
public boolean isDebug() {
244265
return debug;
245266
}
267+
268+
/**
269+
* The context kind of the user that generated this event
270+
* @return the context kind
271+
*/
272+
public String getContextKind() {
273+
return contextKind;
274+
}
275+
}
276+
277+
/**
278+
* An event generated by aliasing users
279+
* @since 5.4.0
280+
*/
281+
public static final class AliasEvent extends Event {
282+
private final String key;
283+
private final String contextKind;
284+
private final String previousKey;
285+
private final String previousContextKind;
286+
287+
/**
288+
* Constructs an alias event.
289+
* @param timestamp when the event was created
290+
* @param user the user being aliased to
291+
* @param previousUser the user being aliased from
292+
*/
293+
public AliasEvent(long timestamp, LDUser user, LDUser previousUser) {
294+
super(timestamp, user);
295+
this.key = user.getKey();
296+
this.contextKind = computeContextKind(user);
297+
this.previousKey = previousUser.getKey();
298+
this.previousContextKind = computeContextKind(previousUser);
299+
}
300+
301+
/**
302+
* Get the key of the user being aliased to
303+
* @return the user key
304+
*/
305+
public String getKey() {
306+
return key;
307+
}
308+
309+
/**
310+
* Get the kind of the user being aliased to
311+
* @return the context kind
312+
*/
313+
public String getContextKind() {
314+
return contextKind;
315+
}
316+
317+
/**
318+
* Get the key of the user being aliased from
319+
* @return the previous user key
320+
*/
321+
public String getPreviousKey() {
322+
return previousKey;
323+
}
324+
325+
/**
326+
* Get the kind of the user being aliased from
327+
* @return the previous context kind
328+
*/
329+
public String getPreviousContextKind() {
330+
return previousContextKind;
331+
}
246332
}
247333
}

src/main/java/com/launchdarkly/sdk/server/interfaces/LDClientInterface.java

Lines changed: 15 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -266,9 +266,23 @@ public interface LDClientInterface extends Closeable {
266266
* For more info: <a href="https://github.com/launchdarkly/js-client#secure-mode">https://github.com/launchdarkly/js-client#secure-mode</a>
267267
* @param user the user to be hashed along with the SDK key
268268
* @return the hash, or null if the hash could not be calculated
269-
*/
269+
*/
270270
String secureModeHash(LDUser user);
271271

272+
/**
273+
* Associates two users for analytics purposes.
274+
*
275+
* This can be helpful in the situation where a person is represented by multiple
276+
* LaunchDarkly users. This may happen, for example, when a person initially logs into
277+
* an application-- the person might be represented by an anonymous user prior to logging
278+
* in and a different user after logging in, as denoted by a different user key.
279+
*
280+
* @param user the newly identified user.
281+
* @param previousUser the previously identified user.
282+
* @since 5.4.0
283+
*/
284+
void alias(LDUser user, LDUser previousUser);
285+
272286
/**
273287
* The current version string of the SDK.
274288
* @return a string in Semantic Versioning 2.0.0 format

0 commit comments

Comments
 (0)