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

Commit 1eebae1

Browse files
authored
prepare 4.8.1 release (#173)
1 parent ab92fc1 commit 1eebae1

File tree

20 files changed

+500
-293
lines changed

20 files changed

+500
-293
lines changed

packaging-test/Makefile

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -145,10 +145,10 @@ $(TEMP_DIR)/dependencies-internal: $(TEMP_DIR)/dependencies-all
145145
rm $@/gson*.jar $@/slf4j*.jar
146146

147147
$(SLF4J_SIMPLE_JAR): | $(TEMP_DIR)
148-
curl $(SLF4J_SIMPLE_JAR_URL) >$@
148+
curl -f -L $(SLF4J_SIMPLE_JAR_URL) >$@
149149

150150
$(FELIX_JAR): | $(TEMP_DIR)
151-
curl $(FELIX_ARCHIVE_URL) >$(TEMP_DIR)/$(FELIX_ARCHIVE)
151+
curl -f -L $(FELIX_ARCHIVE_URL) >$(TEMP_DIR)/$(FELIX_ARCHIVE)
152152
cd $(TEMP_DIR) && tar xfz $(FELIX_ARCHIVE) && rm $(FELIX_ARCHIVE)
153153
cd $(TEMP_DIR) && mv `ls -d felix*` felix
154154

Lines changed: 9 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,9 @@
1+
package com.launchdarkly.shaded.com.newrelic.api.agent;
2+
3+
// Test to verify fix for https://github.com/launchdarkly/java-server-sdk/issues/171
4+
public class NewRelic {
5+
public static void addCustomParameter(String name, String value) {
6+
System.out.println("NewRelic class reference was shaded! Test app loaded " + NewRelic.class.getName());
7+
System.exit(1); // forces test failure
8+
}
9+
}
Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,8 @@
1+
package com.newrelic.api.agent;
2+
3+
// Test to verify fix for https://github.com/launchdarkly/java-server-sdk/issues/171
4+
public class NewRelic {
5+
public static void addCustomParameter(String name, String value) {
6+
System.out.println("NewRelic class reference was correctly resolved without shading");
7+
}
8+
}

packaging-test/test-app/src/main/java/testapp/TestApp.java

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -18,6 +18,9 @@ public static void main(String[] args) throws Exception {
1818
// that provides its own copy of Gson).
1919
JsonPrimitive x = new JsonPrimitive("x");
2020

21+
// Also do a flag evaluation, to ensure that it calls NewRelicReflector.annotateTransaction()
22+
client.boolVariation("flag-key", new LDUser("user-key"), false);
23+
2124
System.out.println("@@@ successfully created LD client @@@");
2225
}
2326
}

src/main/java/com/launchdarkly/client/Components.java

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -118,7 +118,7 @@ public UpdateProcessor createUpdateProcessor(String sdkKey, LDConfig config, Fea
118118
logger.info("Starting LaunchDarkly in LDD mode. Skipping direct feature retrieval.");
119119
return new UpdateProcessor.NullUpdateProcessor();
120120
} else {
121-
FeatureRequestor requestor = new FeatureRequestor(sdkKey, config);
121+
DefaultFeatureRequestor requestor = new DefaultFeatureRequestor(sdkKey, config);
122122
if (config.stream) {
123123
logger.info("Enabling streaming API");
124124
return new StreamProcessor(sdkKey, config, requestor, featureStore, null);

src/main/java/com/launchdarkly/client/DefaultEventProcessor.java

Lines changed: 15 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -25,11 +25,14 @@
2525
import java.util.concurrent.atomic.AtomicInteger;
2626
import java.util.concurrent.atomic.AtomicLong;
2727

28+
import static com.launchdarkly.client.Util.configureHttpClientBuilder;
2829
import static com.launchdarkly.client.Util.getRequestBuilder;
2930
import static com.launchdarkly.client.Util.httpErrorMessage;
3031
import static com.launchdarkly.client.Util.isHttpErrorRecoverable;
32+
import static com.launchdarkly.client.Util.shutdownHttpClient;
3133

3234
import okhttp3.MediaType;
35+
import okhttp3.OkHttpClient;
3336
import okhttp3.Request;
3437
import okhttp3.RequestBody;
3538
import okhttp3.Response;
@@ -46,7 +49,7 @@ final class DefaultEventProcessor implements EventProcessor {
4649

4750
DefaultEventProcessor(String sdkKey, LDConfig config) {
4851
inbox = new ArrayBlockingQueue<>(config.capacity);
49-
52+
5053
ThreadFactory threadFactory = new ThreadFactoryBuilder()
5154
.setDaemon(true)
5255
.setNameFormat("LaunchDarkly-EventProcessor-%d")
@@ -181,6 +184,7 @@ static final class EventDispatcher {
181184
private static final int MESSAGE_BATCH_SIZE = 50;
182185

183186
private final LDConfig config;
187+
private final OkHttpClient httpClient;
184188
private final List<SendEventsTask> flushWorkers;
185189
private final AtomicInteger busyFlushWorkersCount;
186190
private final Random random = new Random();
@@ -194,6 +198,10 @@ private EventDispatcher(String sdkKey, LDConfig config,
194198
this.config = config;
195199
this.busyFlushWorkersCount = new AtomicInteger(0);
196200

201+
OkHttpClient.Builder httpBuilder = new OkHttpClient.Builder();
202+
configureHttpClientBuilder(config, httpBuilder);
203+
httpClient = httpBuilder.build();
204+
197205
// This queue only holds one element; it represents a flush task that has not yet been
198206
// picked up by any worker, so if we try to push another one and are refused, it means
199207
// all the workers are busy.
@@ -236,7 +244,7 @@ public void handleResponse(Response response, Date responseDate) {
236244
}
237245
};
238246
for (int i = 0; i < MAX_FLUSH_THREADS; i++) {
239-
SendEventsTask task = new SendEventsTask(sdkKey, config, listener, payloadQueue,
247+
SendEventsTask task = new SendEventsTask(sdkKey, config, httpClient, listener, payloadQueue,
240248
busyFlushWorkersCount, threadFactory);
241249
flushWorkers.add(task);
242250
}
@@ -291,8 +299,7 @@ private void doShutdown() {
291299
for (SendEventsTask task: flushWorkers) {
292300
task.stop();
293301
}
294-
// Note that we don't close the HTTP client here, because it's shared by other components
295-
// via the LDConfig. The LDClient will dispose of it.
302+
shutdownHttpClient(httpClient);
296303
}
297304

298305
private void waitUntilAllFlushWorkersInactive() {
@@ -477,6 +484,7 @@ private static interface EventResponseListener {
477484
private static final class SendEventsTask implements Runnable {
478485
private final String sdkKey;
479486
private final LDConfig config;
487+
private final OkHttpClient httpClient;
480488
private final EventResponseListener responseListener;
481489
private final BlockingQueue<FlushPayload> payloadQueue;
482490
private final AtomicInteger activeFlushWorkersCount;
@@ -485,11 +493,12 @@ private static final class SendEventsTask implements Runnable {
485493
private final Thread thread;
486494
private final SimpleDateFormat httpDateFormat = new SimpleDateFormat("EEE, dd MMM yyyy HH:mm:ss zzz"); // need one instance per task because the date parser isn't thread-safe
487495

488-
SendEventsTask(String sdkKey, LDConfig config, EventResponseListener responseListener,
496+
SendEventsTask(String sdkKey, LDConfig config, OkHttpClient httpClient, EventResponseListener responseListener,
489497
BlockingQueue<FlushPayload> payloadQueue, AtomicInteger activeFlushWorkersCount,
490498
ThreadFactory threadFactory) {
491499
this.sdkKey = sdkKey;
492500
this.config = config;
501+
this.httpClient = httpClient;
493502
this.formatter = new EventOutput.Formatter(config.inlineUsersInEvents);
494503
this.responseListener = responseListener;
495504
this.payloadQueue = payloadQueue;
@@ -551,7 +560,7 @@ private void postEvents(List<EventOutput> eventsOut) {
551560
.build();
552561

553562
long startTime = System.currentTimeMillis();
554-
try (Response response = config.httpClient.newCall(request).execute()) {
563+
try (Response response = httpClient.newCall(request).execute()) {
555564
long endTime = System.currentTimeMillis();
556565
logger.debug("Event delivery took {} ms, response status {}", endTime - startTime, response.code());
557566
if (!response.isSuccessful()) {
Lines changed: 103 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,103 @@
1+
package com.launchdarkly.client;
2+
3+
import com.google.common.io.Files;
4+
5+
import org.slf4j.Logger;
6+
import org.slf4j.LoggerFactory;
7+
8+
import java.io.File;
9+
import java.io.IOException;
10+
import java.util.HashMap;
11+
import java.util.Map;
12+
13+
import static com.launchdarkly.client.Util.configureHttpClientBuilder;
14+
import static com.launchdarkly.client.Util.getRequestBuilder;
15+
import static com.launchdarkly.client.Util.shutdownHttpClient;
16+
import static com.launchdarkly.client.VersionedDataKind.FEATURES;
17+
import static com.launchdarkly.client.VersionedDataKind.SEGMENTS;
18+
19+
import okhttp3.Cache;
20+
import okhttp3.OkHttpClient;
21+
import okhttp3.Request;
22+
import okhttp3.Response;
23+
24+
class DefaultFeatureRequestor implements FeatureRequestor {
25+
private static final Logger logger = LoggerFactory.getLogger(DefaultFeatureRequestor.class);
26+
private static final String GET_LATEST_FLAGS_PATH = "/sdk/latest-flags";
27+
private static final String GET_LATEST_SEGMENTS_PATH = "/sdk/latest-segments";
28+
private static final String GET_LATEST_ALL_PATH = "/sdk/latest-all";
29+
private static final long MAX_HTTP_CACHE_SIZE_BYTES = 10 * 1024 * 1024; // 10 MB
30+
31+
private final String sdkKey;
32+
private final LDConfig config;
33+
private final OkHttpClient httpClient;
34+
35+
DefaultFeatureRequestor(String sdkKey, LDConfig config) {
36+
this.sdkKey = sdkKey;
37+
this.config = config;
38+
39+
OkHttpClient.Builder httpBuilder = new OkHttpClient.Builder();
40+
configureHttpClientBuilder(config, httpBuilder);
41+
42+
// HTTP caching is used only for FeatureRequestor. However, when streaming is enabled, HTTP GETs
43+
// made by FeatureRequester will always guarantee a new flag state, so we disable the cache.
44+
if (!config.stream) {
45+
File cacheDir = Files.createTempDir();
46+
Cache cache = new Cache(cacheDir, MAX_HTTP_CACHE_SIZE_BYTES);
47+
httpBuilder.cache(cache);
48+
}
49+
50+
httpClient = httpBuilder.build();
51+
}
52+
53+
public void close() {
54+
shutdownHttpClient(httpClient);
55+
}
56+
57+
public FeatureFlag getFlag(String featureKey) throws IOException, HttpErrorException {
58+
String body = get(GET_LATEST_FLAGS_PATH + "/" + featureKey);
59+
return config.gson.fromJson(body, FeatureFlag.class);
60+
}
61+
62+
public Segment getSegment(String segmentKey) throws IOException, HttpErrorException {
63+
String body = get(GET_LATEST_SEGMENTS_PATH + "/" + segmentKey);
64+
return config.gson.fromJson(body, Segment.class);
65+
}
66+
67+
public AllData getAllData() throws IOException, HttpErrorException {
68+
String body = get(GET_LATEST_ALL_PATH);
69+
return config.gson.fromJson(body, AllData.class);
70+
}
71+
72+
static Map<VersionedDataKind<?>, Map<String, ? extends VersionedData>> toVersionedDataMap(AllData allData) {
73+
Map<VersionedDataKind<?>, Map<String, ? extends VersionedData>> ret = new HashMap<>();
74+
ret.put(FEATURES, allData.flags);
75+
ret.put(SEGMENTS, allData.segments);
76+
return ret;
77+
}
78+
79+
private String get(String path) throws IOException, HttpErrorException {
80+
Request request = getRequestBuilder(sdkKey)
81+
.url(config.baseURI.resolve(path).toURL())
82+
.get()
83+
.build();
84+
85+
logger.debug("Making request: " + request);
86+
87+
try (Response response = httpClient.newCall(request).execute()) {
88+
String body = response.body().string();
89+
90+
if (!response.isSuccessful()) {
91+
throw new HttpErrorException(response.code());
92+
}
93+
logger.debug("Get flag(s) response: " + response.toString() + " with body: " + body);
94+
logger.debug("Network response: " + response.networkResponse());
95+
if(!config.stream) {
96+
logger.debug("Cache hit count: " + httpClient.cache().hitCount() + " Cache network Count: " + httpClient.cache().networkCount());
97+
logger.debug("Cache response: " + response.cacheResponse());
98+
}
99+
100+
return body;
101+
}
102+
}
103+
}
Lines changed: 7 additions & 70 deletions
Original file line numberDiff line numberDiff line change
@@ -1,26 +1,15 @@
11
package com.launchdarkly.client;
22

3-
import org.slf4j.Logger;
4-
import org.slf4j.LoggerFactory;
5-
3+
import java.io.Closeable;
64
import java.io.IOException;
7-
import java.util.HashMap;
85
import java.util.Map;
96

10-
import static com.launchdarkly.client.Util.getRequestBuilder;
11-
import static com.launchdarkly.client.VersionedDataKind.FEATURES;
12-
import static com.launchdarkly.client.VersionedDataKind.SEGMENTS;
7+
interface FeatureRequestor extends Closeable {
8+
FeatureFlag getFlag(String featureKey) throws IOException, HttpErrorException;
139

14-
import okhttp3.Request;
15-
import okhttp3.Response;
10+
Segment getSegment(String segmentKey) throws IOException, HttpErrorException;
1611

17-
class FeatureRequestor {
18-
private static final Logger logger = LoggerFactory.getLogger(FeatureRequestor.class);
19-
private static final String GET_LATEST_FLAGS_PATH = "/sdk/latest-flags";
20-
private static final String GET_LATEST_SEGMENTS_PATH = "/sdk/latest-segments";
21-
private static final String GET_LATEST_ALL_PATH = "/sdk/latest-all";
22-
private final String sdkKey;
23-
private final LDConfig config;
12+
AllData getAllData() throws IOException, HttpErrorException;
2413

2514
static class AllData {
2615
final Map<String, FeatureFlag> flags;
@@ -30,57 +19,5 @@ static class AllData {
3019
this.flags = flags;
3120
this.segments = segments;
3221
}
33-
}
34-
35-
FeatureRequestor(String sdkKey, LDConfig config) {
36-
this.sdkKey = sdkKey;
37-
this.config = config;
38-
}
39-
40-
FeatureFlag getFlag(String featureKey) throws IOException, HttpErrorException {
41-
String body = get(GET_LATEST_FLAGS_PATH + "/" + featureKey);
42-
return config.gson.fromJson(body, FeatureFlag.class);
43-
}
44-
45-
Segment getSegment(String segmentKey) throws IOException, HttpErrorException {
46-
String body = get(GET_LATEST_SEGMENTS_PATH + "/" + segmentKey);
47-
return config.gson.fromJson(body, Segment.class);
48-
}
49-
50-
AllData getAllData() throws IOException, HttpErrorException {
51-
String body = get(GET_LATEST_ALL_PATH);
52-
return config.gson.fromJson(body, AllData.class);
53-
}
54-
55-
static Map<VersionedDataKind<?>, Map<String, ? extends VersionedData>> toVersionedDataMap(AllData allData) {
56-
Map<VersionedDataKind<?>, Map<String, ? extends VersionedData>> ret = new HashMap<>();
57-
ret.put(FEATURES, allData.flags);
58-
ret.put(SEGMENTS, allData.segments);
59-
return ret;
60-
}
61-
62-
private String get(String path) throws IOException, HttpErrorException {
63-
Request request = getRequestBuilder(sdkKey)
64-
.url(config.baseURI.resolve(path).toURL())
65-
.get()
66-
.build();
67-
68-
logger.debug("Making request: " + request);
69-
70-
try (Response response = config.httpClient.newCall(request).execute()) {
71-
String body = response.body().string();
72-
73-
if (!response.isSuccessful()) {
74-
throw new HttpErrorException(response.code());
75-
}
76-
logger.debug("Get flag(s) response: " + response.toString() + " with body: " + body);
77-
logger.debug("Network response: " + response.networkResponse());
78-
if(!config.stream) {
79-
logger.debug("Cache hit count: " + config.httpClient.cache().hitCount() + " Cache network Count: " + config.httpClient.cache().networkCount());
80-
logger.debug("Cache response: " + response.cacheResponse());
81-
}
82-
83-
return body;
84-
}
85-
}
86-
}
22+
}
23+
}

src/main/java/com/launchdarkly/client/LDClient.java

Lines changed: 3 additions & 14 deletions
Original file line numberDiff line numberDiff line change
@@ -22,6 +22,7 @@
2222
import javax.crypto.Mac;
2323
import javax.crypto.spec.SecretKeySpec;
2424

25+
import static com.google.common.base.Preconditions.checkNotNull;
2526
import static com.launchdarkly.client.VersionedDataKind.FEATURES;
2627

2728
/**
@@ -58,8 +59,8 @@ public LDClient(String sdkKey) {
5859
* @param config a client configuration object
5960
*/
6061
public LDClient(String sdkKey, LDConfig config) {
61-
this.config = config;
62-
this.sdkKey = sdkKey;
62+
this.config = checkNotNull(config, "config must not be null");
63+
this.sdkKey = checkNotNull(sdkKey, "sdkKey must not be null");
6364

6465
FeatureStore store;
6566
if (config.deprecatedFeatureStore != null) {
@@ -357,18 +358,6 @@ public void close() throws IOException {
357358
}
358359
this.eventProcessor.close();
359360
this.updateProcessor.close();
360-
if (this.config.httpClient != null) {
361-
if (this.config.httpClient.dispatcher() != null && this.config.httpClient.dispatcher().executorService() != null) {
362-
this.config.httpClient.dispatcher().cancelAll();
363-
this.config.httpClient.dispatcher().executorService().shutdownNow();
364-
}
365-
if (this.config.httpClient.connectionPool() != null) {
366-
this.config.httpClient.connectionPool().evictAll();
367-
}
368-
if (this.config.httpClient.cache() != null) {
369-
this.config.httpClient.cache().close();
370-
}
371-
}
372361
}
373362

374363
@Override

0 commit comments

Comments
 (0)