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

Commit a7b102a

Browse files
authored
Merge pull request #65 from launchdarkly/dr/evenMoreV2
Even more v2 things.
2 parents 462e8ab + fdc3543 commit a7b102a

File tree

10 files changed

+120
-83
lines changed

10 files changed

+120
-83
lines changed

README.md

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -18,9 +18,9 @@ Quick setup
1818

1919
import com.launchdarkly.client.*;
2020

21-
2. Create a new LDClient with your API key:
21+
2. Create a new LDClient with your SDK key:
2222

23-
LDClient ldClient = new LDClient("YOUR_API_KEY");
23+
LDClient ldClient = new LDClient("YOUR_SDK_KEY");
2424

2525
Your first feature flag
2626
-----------------------

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

Lines changed: 4 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -21,12 +21,12 @@ class EventProcessor implements Closeable {
2121
private final ScheduledExecutorService scheduler;
2222
private final Random random = new Random();
2323
private final BlockingQueue<Event> queue;
24-
private final String apiKey;
24+
private final String sdkKey;
2525
private final LDConfig config;
2626
private final Consumer consumer;
2727

28-
EventProcessor(String apiKey, LDConfig config) {
29-
this.apiKey = apiKey;
28+
EventProcessor(String sdkKey, LDConfig config) {
29+
this.sdkKey = sdkKey;
3030
this.queue = new ArrayBlockingQueue<>(config.capacity);
3131
this.consumer = new Consumer(config);
3232
this.config = config;
@@ -85,7 +85,7 @@ private void postEvents(List<Event> events) {
8585
Gson gson = new Gson();
8686
String json = gson.toJson(events);
8787

88-
HttpPost request = config.postEventsRequest(apiKey, "/bulk");
88+
HttpPost request = config.postEventsRequest(sdkKey, "/bulk");
8989
StringEntity entity = new StringEntity(json, "UTF-8");
9090
entity.setContentType("application/json");
9191
request.setEntity(entity);

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

Lines changed: 12 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -53,9 +53,19 @@ static Map<String, FeatureFlag> fromJsonMap(String json) {
5353
}
5454

5555
EvalResult evaluate(LDUser user, FeatureStore featureStore) throws EvaluationException {
56+
if (user == null || user.getKeyAsString().isEmpty()) {
57+
logger.warn("Null user or null/empty user key when evaluating flag: " + key + "; returning null");
58+
return null;
59+
}
5660
List<FeatureRequestEvent> prereqEvents = new ArrayList<>();
57-
JsonElement value = evaluate(user, featureStore, prereqEvents);
58-
return new EvalResult(value, prereqEvents);
61+
if (isOn()) {
62+
JsonElement value = evaluate(user, featureStore, prereqEvents);
63+
if (value != null) {
64+
return new EvalResult(value, prereqEvents);
65+
}
66+
}
67+
JsonElement offVariation = getOffVariationValue();
68+
return new EvalResult(offVariation, prereqEvents);
5969
}
6070

6171
// Returning either a JsonElement or null indicating prereq failure/error.

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

Lines changed: 5 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -19,13 +19,13 @@
1919
class FeatureRequestor {
2020

2121
public static final String GET_LATEST_FLAGS_PATH = "/sdk/latest-flags";
22-
private final String apiKey;
22+
private final String sdkKey;
2323
private final LDConfig config;
2424
private final CloseableHttpClient client;
2525
private static final Logger logger = LoggerFactory.getLogger(FeatureRequestor.class);
2626

27-
FeatureRequestor(String apiKey, LDConfig config) {
28-
this.apiKey = apiKey;
27+
FeatureRequestor(String sdkKey, LDConfig config) {
28+
this.sdkKey = sdkKey;
2929
this.config = config;
3030
this.client = createClient();
3131
}
@@ -58,7 +58,7 @@ protected CloseableHttpClient createClient() {
5858
Map<String, FeatureFlag> makeAllRequest() throws IOException {
5959
HttpCacheContext context = HttpCacheContext.create();
6060

61-
HttpGet request = config.getRequest(apiKey, GET_LATEST_FLAGS_PATH);
61+
HttpGet request = config.getRequest(sdkKey, GET_LATEST_FLAGS_PATH);
6262

6363
CloseableHttpResponse response = null;
6464
try {
@@ -107,7 +107,7 @@ FeatureFlag makeRequest(String featureKey, boolean latest) throws IOException {
107107

108108
String resource = latest ? "/api/eval/latest-features/" : "/api/eval/features/";
109109

110-
HttpGet request = config.getRequest(apiKey,resource + featureKey);
110+
HttpGet request = config.getRequest(sdkKey,resource + featureKey);
111111

112112
CloseableHttpResponse response = null;
113113
try {

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

Lines changed: 71 additions & 40 deletions
Original file line numberDiff line numberDiff line change
@@ -4,13 +4,19 @@
44
import com.google.common.annotations.VisibleForTesting;
55
import com.google.gson.JsonElement;
66
import com.google.gson.JsonPrimitive;
7+
import org.apache.commons.codec.binary.Hex;
78
import org.apache.http.annotation.ThreadSafe;
89
import org.slf4j.Logger;
910
import org.slf4j.LoggerFactory;
1011

12+
import javax.crypto.Mac;
13+
import javax.crypto.spec.SecretKeySpec;
1114
import java.io.Closeable;
1215
import java.io.IOException;
16+
import java.io.UnsupportedEncodingException;
1317
import java.net.URL;
18+
import java.security.InvalidKeyException;
19+
import java.security.NoSuchAlgorithmException;
1420
import java.util.HashMap;
1521
import java.util.Map;
1622
import java.util.concurrent.Future;
@@ -26,33 +32,37 @@
2632
@ThreadSafe
2733
public class LDClient implements Closeable {
2834
private static final Logger logger = LoggerFactory.getLogger(LDClient.class);
35+
private static final String HMAC_ALGORITHM = "HmacSHA256";
36+
protected static final String CLIENT_VERSION = getClientVersion();
37+
2938
private final LDConfig config;
39+
private final String sdkKey;
3040
private final FeatureRequestor requestor;
3141
private final EventProcessor eventProcessor;
3242
private UpdateProcessor updateProcessor;
33-
protected static final String CLIENT_VERSION = getClientVersion();
3443

3544
/**
3645
* Creates a new client instance that connects to LaunchDarkly with the default configuration. In most
3746
* cases, you should use this constructor.
3847
*
39-
* @param apiKey the API key for your account
48+
* @param sdkKey the SDK key for your LaunchDarkly environment
4049
*/
41-
public LDClient(String apiKey) {
42-
this(apiKey, LDConfig.DEFAULT);
50+
public LDClient(String sdkKey) {
51+
this(sdkKey, LDConfig.DEFAULT);
4352
}
4453

4554
/**
4655
* Creates a new client to connect to LaunchDarkly with a custom configuration. This constructor
4756
* can be used to configure advanced client features, such as customizing the LaunchDarkly base URL.
4857
*
49-
* @param apiKey the API key for your account
58+
* @param sdkKey the SDK key for your LaunchDarkly environment
5059
* @param config a client configuration object
5160
*/
52-
public LDClient(String apiKey, LDConfig config) {
61+
public LDClient(String sdkKey, LDConfig config) {
5362
this.config = config;
54-
this.requestor = createFeatureRequestor(apiKey, config);
55-
this.eventProcessor = createEventProcessor(apiKey, config);
63+
this.sdkKey = sdkKey;
64+
this.requestor = createFeatureRequestor(sdkKey, config);
65+
this.eventProcessor = createEventProcessor(sdkKey, config);
5666

5767
if (config.offline) {
5868
logger.info("Starting LaunchDarkly client in offline mode");
@@ -66,7 +76,7 @@ public LDClient(String apiKey, LDConfig config) {
6676

6777
if (config.stream) {
6878
logger.info("Enabling streaming API");
69-
this.updateProcessor = createStreamProcessor(apiKey, config, requestor);
79+
this.updateProcessor = createStreamProcessor(sdkKey, config, requestor);
7080
} else {
7181
logger.info("Disabling streaming API");
7282
this.updateProcessor = createPollingProcessor(config);
@@ -91,18 +101,18 @@ public boolean initialized() {
91101
}
92102

93103
@VisibleForTesting
94-
protected FeatureRequestor createFeatureRequestor(String apiKey, LDConfig config) {
95-
return new FeatureRequestor(apiKey, config);
104+
protected FeatureRequestor createFeatureRequestor(String sdkKey, LDConfig config) {
105+
return new FeatureRequestor(sdkKey, config);
96106
}
97107

98108
@VisibleForTesting
99-
protected EventProcessor createEventProcessor(String apiKey, LDConfig config) {
100-
return new EventProcessor(apiKey, config);
109+
protected EventProcessor createEventProcessor(String sdkKey, LDConfig config) {
110+
return new EventProcessor(sdkKey, config);
101111
}
102112

103113
@VisibleForTesting
104-
protected StreamProcessor createStreamProcessor(String apiKey, LDConfig config, FeatureRequestor requestor) {
105-
return new StreamProcessor(apiKey, config, requestor);
114+
protected StreamProcessor createStreamProcessor(String sdkKey, LDConfig config, FeatureRequestor requestor) {
115+
return new StreamProcessor(sdkKey, config, requestor);
106116
}
107117

108118
@VisibleForTesting
@@ -174,33 +184,42 @@ private void sendFlagRequestEvent(String featureKey, LDUser user, JsonElement va
174184
}
175185

176186
/**
177-
* Returns a map from feature flag keys to Boolean feature flag values for a given user. The map will contain {@code null}
178-
* entries for any flags that are off or for any feature flags with non-boolean variations. If the client is offline or
179-
* has not been initialized, a {@code null} map will be returned.
187+
* Returns a map from feature flag keys to {@code JsonElement} feature flag values for a given user.
188+
* If the result of a flag's evaluation would have returned the default variation, it will have a null entry
189+
* in the map. If the client is offline, has not been initialized, or a null user or user with null/empty user key a {@code null} map will be returned.
180190
* This method will not send analytics events back to LaunchDarkly.
181191
* <p>
182192
* The most common use case for this method is to bootstrap a set of client-side feature flags from a back-end service.
183193
*
184194
* @param user the end user requesting the feature flags
185-
* @return a map from feature flag keys to JsonElement values for the specified user
195+
* @return a map from feature flag keys to {@code JsonElement} for the specified user
186196
*/
187-
public Map<String, Boolean> allFlags(LDUser user) {
197+
public Map<String, JsonElement> allFlags(LDUser user) {
188198
if (isOffline()) {
199+
logger.warn("allFlags() was called when client is in offline mode! Returning null.");
189200
return null;
190201
}
191202

192203
if (!initialized()) {
204+
logger.warn("allFlags() was called before Client has been initialized! Returning null.");
205+
return null;
206+
}
207+
208+
if (user == null || user.getKeyAsString().isEmpty()) {
209+
logger.warn("allFlags() was called with null user or null/empty user key! returning null");
193210
return null;
194211
}
195212

196213
Map<String, FeatureFlag> flags = this.config.featureStore.all();
197-
Map<String, Boolean> result = new HashMap<>();
214+
Map<String, JsonElement> result = new HashMap<>();
198215

199-
for (String key : flags.keySet()) {
200-
JsonElement evalResult = evaluate(key, user, null);
201-
if (evalResult.isJsonPrimitive() && evalResult.getAsJsonPrimitive().isBoolean()) {
202-
result.put(key, evalResult.getAsBoolean());
216+
for (Map.Entry<String, FeatureFlag> entry : flags.entrySet()) {
217+
try {
218+
JsonElement evalResult = entry.getValue().evaluate(user, config.featureStore).getValue();
219+
result.put(entry.getKey(), evalResult);
203220

221+
} catch (EvaluationException e) {
222+
logger.error("Exception caught when evaluating all flags:", e);
204223
}
205224
}
206225
return result;
@@ -315,22 +334,15 @@ private JsonElement evaluate(String featureKey, LDUser user, JsonElement default
315334
sendFlagRequestEvent(featureKey, user, defaultValue, defaultValue, null);
316335
return defaultValue;
317336
}
318-
if (featureFlag.isOn()) {
319-
FeatureFlag.EvalResult evalResult = featureFlag.evaluate(user, config.featureStore);
320-
if (!isOffline()) {
321-
for (FeatureRequestEvent event : evalResult.getPrerequisiteEvents()) {
322-
eventProcessor.sendEvent(event);
323-
}
324-
}
325-
if (evalResult.getValue() != null) {
326-
sendFlagRequestEvent(featureKey, user, evalResult.getValue(), defaultValue, featureFlag.getVersion());
327-
return evalResult.getValue();
328-
}
337+
FeatureFlag.EvalResult evalResult = featureFlag.evaluate(user, config.featureStore);
338+
if (!isOffline()) {
339+
for (FeatureRequestEvent event : evalResult.getPrerequisiteEvents()) {
340+
eventProcessor.sendEvent(event);
341+
}
329342
}
330-
JsonElement offVariation = featureFlag.getOffVariationValue();
331-
if (offVariation != null) {
332-
sendFlagRequestEvent(featureKey, user, offVariation, defaultValue, featureFlag.getVersion());
333-
return offVariation;
343+
if (evalResult.getValue() != null) {
344+
sendFlagRequestEvent(featureKey, user, evalResult.getValue(), defaultValue, featureFlag.getVersion());
345+
return evalResult.getValue();
334346
}
335347
} catch (Exception e) {
336348
logger.error("Encountered exception in LaunchDarkly client", e);
@@ -368,6 +380,25 @@ public boolean isOffline() {
368380
return config.offline;
369381
}
370382

383+
/**
384+
* For more info: <a href=https://github.com/launchdarkly/js-client#secure-mode>https://github.com/launchdarkly/js-client#secure-mode</a>
385+
* @param user The User to be hashed along with the sdk key
386+
* @return the hash, or null if the hash could not be calculated.
387+
*/
388+
public String secureModeHash(LDUser user) {
389+
if (user == null || user.getKeyAsString().isEmpty()) {
390+
return null;
391+
}
392+
try {
393+
Mac mac = Mac.getInstance(HMAC_ALGORITHM);
394+
mac.init(new SecretKeySpec(sdkKey.getBytes(), HMAC_ALGORITHM));
395+
return Hex.encodeHexString(mac.doFinal(user.getKeyAsString().getBytes("UTF8")));
396+
} catch (InvalidKeyException | UnsupportedEncodingException | NoSuchAlgorithmException e) {
397+
logger.error("Could not generate secure mode hash", e);
398+
}
399+
return null;
400+
}
401+
371402
private static String getClientVersion() {
372403
Class clazz = LDConfig.class;
373404
String className = clazz.getSimpleName() + ".class";

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

Lines changed: 4 additions & 19 deletions
Original file line numberDiff line numberDiff line change
@@ -376,12 +376,12 @@ private URIBuilder getEventsBuilder() {
376376
.setPort(eventsURI.getPort());
377377
}
378378

379-
HttpGet getRequest(String apiKey, String path) {
379+
HttpGet getRequest(String sdkKey, String path) {
380380
URIBuilder builder = this.getBuilder().setPath(path);
381381

382382
try {
383383
HttpGet request = new HttpGet(builder.build());
384-
request.addHeader("Authorization", "api_key " + apiKey);
384+
request.addHeader("Authorization", sdkKey);
385385
request.addHeader("User-Agent", "JavaClient/" + LDClient.CLIENT_VERSION);
386386

387387
return request;
@@ -391,27 +391,12 @@ HttpGet getRequest(String apiKey, String path) {
391391
}
392392
}
393393

394-
HttpPost postRequest(String apiKey, String path) {
395-
URIBuilder builder = this.getBuilder().setPath(path);
396-
397-
try {
398-
HttpPost request = new HttpPost(builder.build());
399-
request.addHeader("Authorization", "api_key " + apiKey);
400-
request.addHeader("User-Agent", "JavaClient/" + LDClient.CLIENT_VERSION);
401-
402-
return request;
403-
} catch (Exception e) {
404-
logger.error("Unhandled exception in LaunchDarkly client", e);
405-
return null;
406-
}
407-
}
408-
409-
HttpPost postEventsRequest(String apiKey, String path) {
394+
HttpPost postEventsRequest(String sdkKey, String path) {
410395
URIBuilder builder = this.getEventsBuilder().setPath(eventsURI.getPath() + path);
411396

412397
try {
413398
HttpPost request = new HttpPost(builder.build());
414-
request.addHeader("Authorization", "api_key " + apiKey);
399+
request.addHeader("Authorization", sdkKey);
415400
request.addHeader("User-Agent", "JavaClient/" + LDClient.CLIENT_VERSION);
416401

417402
return request;

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

Lines changed: 6 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -23,16 +23,16 @@ class StreamProcessor implements UpdateProcessor {
2323

2424
private final FeatureStore store;
2525
private final LDConfig config;
26-
private final String apiKey;
26+
private final String sdkKey;
2727
private final FeatureRequestor requestor;
2828
private EventSource es;
2929
private AtomicBoolean initialized = new AtomicBoolean(false);
3030

3131

32-
StreamProcessor(String apiKey, LDConfig config, FeatureRequestor requestor) {
32+
StreamProcessor(String sdkKey, LDConfig config, FeatureRequestor requestor) {
3333
this.store = config.featureStore;
3434
this.config = config;
35-
this.apiKey = apiKey;
35+
this.sdkKey = sdkKey;
3636
this.requestor = requestor;
3737
}
3838

@@ -41,7 +41,7 @@ public Future<Void> start() {
4141
final VeryBasicFuture initFuture = new VeryBasicFuture();
4242

4343
Headers headers = new Headers.Builder()
44-
.add("Authorization", "api_key " + this.apiKey)
44+
.add("Authorization", this.sdkKey)
4545
.add("User-Agent", "JavaClient/" + LDClient.CLIENT_VERSION)
4646
.add("Accept", "text/event-stream")
4747
.build();
@@ -102,7 +102,8 @@ public void onMessage(String name, MessageEvent event) throws Exception {
102102

103103
@Override
104104
public void onError(Throwable throwable) {
105-
logger.warn("Encountered EventSource error", throwable);
105+
logger.error("Encountered EventSource error: " + throwable.getMessage());
106+
logger.debug("", throwable);
106107
}
107108
};
108109

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

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -43,7 +43,7 @@ static boolean handleResponse(Logger logger, HttpRequestBase request, CloseableH
4343
return true;
4444
}
4545
if (statusCode == HttpStatus.SC_UNAUTHORIZED) {
46-
logger.error("[401] Invalid API key when accessing URI: " + request.getURI().toString());
46+
logger.error("[401] Invalid SDK key when accessing URI: " + request.getURI().toString());
4747
} else {
4848
logger.error("[" + statusCode + "] " + response.getStatusLine().getReasonPhrase() + " When accessing URI: " + request.getURI().toString());
4949
}

0 commit comments

Comments
 (0)