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

Commit

Permalink
prepare 4.2.0 release (#129)
Browse files Browse the repository at this point in the history
  • Loading branch information
eli-darkly authored Jun 26, 2018
1 parent 3360dfb commit 5bb05d4
Show file tree
Hide file tree
Showing 15 changed files with 375 additions and 117 deletions.
2 changes: 1 addition & 1 deletion src/main/java/com/launchdarkly/client/Components.java
Original file line number Diff line number Diff line change
Expand Up @@ -113,7 +113,7 @@ public UpdateProcessor createUpdateProcessor(String sdkKey, LDConfig config, Fea
FeatureRequestor requestor = new FeatureRequestor(sdkKey, config);
if (config.stream) {
logger.info("Enabling streaming API");
return new StreamProcessor(sdkKey, config, requestor, featureStore);
return new StreamProcessor(sdkKey, config, requestor, featureStore, null);
} else {
logger.info("Disabling streaming API");
logger.warn("You should only disable the streaming API if instructed to do so by LaunchDarkly support");
Expand Down
11 changes: 8 additions & 3 deletions src/main/java/com/launchdarkly/client/DefaultEventProcessor.java
Original file line number Diff line number Diff line change
Expand Up @@ -25,6 +25,8 @@
import java.util.concurrent.atomic.AtomicLong;

import static com.launchdarkly.client.Util.getRequestBuilder;
import static com.launchdarkly.client.Util.httpErrorMessage;
import static com.launchdarkly.client.Util.isHttpErrorRecoverable;

import okhttp3.MediaType;
import okhttp3.Request;
Expand Down Expand Up @@ -386,9 +388,12 @@ private void handleResponse(Response response) {
} catch (ParseException e) {
}
}
if (response.code() == 401) {
if (!isHttpErrorRecoverable(response.code())) {
disabled.set(true);
logger.error("Received 401 error, no further events will be posted since SDK key is invalid");
logger.error(httpErrorMessage(response.code(), "posting events", "some events were dropped"));
// It's "some events were dropped" because we're not going to retry *this* request any more times -
// we only get to this point if we have used up our retry attempts. So the last batch of events was
// lost, even though we will still try to post *other* events in the future.
}
}
}
Expand Down Expand Up @@ -530,7 +535,7 @@ private void postEvents(List<EventOutput> eventsOut) {
logger.debug("Event delivery took {} ms, response status {}", endTime - startTime, response.code());
if (!response.isSuccessful()) {
logger.warn("Unexpected response status when posting events: {}", response.code());
if (response.code() >= 500) {
if (isHttpErrorRecoverable(response.code())) {
continue;
}
}
Expand Down
25 changes: 7 additions & 18 deletions src/main/java/com/launchdarkly/client/FeatureRequestor.java
Original file line number Diff line number Diff line change
Expand Up @@ -37,27 +37,27 @@ static class AllData {
this.config = config;
}

Map<String, FeatureFlag> getAllFlags() throws IOException, InvalidSDKKeyException {
Map<String, FeatureFlag> getAllFlags() throws IOException, HttpErrorException {
String body = get(GET_LATEST_FLAGS_PATH);
return FeatureFlag.fromJsonMap(config, body);
}

FeatureFlag getFlag(String featureKey) throws IOException, InvalidSDKKeyException {
FeatureFlag getFlag(String featureKey) throws IOException, HttpErrorException {
String body = get(GET_LATEST_FLAGS_PATH + "/" + featureKey);
return FeatureFlag.fromJson(config, body);
}

Map<String, Segment> getAllSegments() throws IOException, InvalidSDKKeyException {
Map<String, Segment> getAllSegments() throws IOException, HttpErrorException {
String body = get(GET_LATEST_SEGMENTS_PATH);
return Segment.fromJsonMap(config, body);
}

Segment getSegment(String segmentKey) throws IOException, InvalidSDKKeyException {
Segment getSegment(String segmentKey) throws IOException, HttpErrorException {
String body = get(GET_LATEST_SEGMENTS_PATH + "/" + segmentKey);
return Segment.fromJson(config, body);
}

AllData getAllData() throws IOException, InvalidSDKKeyException {
AllData getAllData() throws IOException, HttpErrorException {
String body = get(GET_LATEST_ALL_PATH);
return config.gson.fromJson(body, AllData.class);
}
Expand All @@ -69,7 +69,7 @@ AllData getAllData() throws IOException, InvalidSDKKeyException {
return ret;
}

private String get(String path) throws IOException, InvalidSDKKeyException {
private String get(String path) throws IOException, HttpErrorException {
Request request = getRequestBuilder(sdkKey)
.url(config.baseURI.toString() + path)
.get()
Expand All @@ -81,12 +81,7 @@ private String get(String path) throws IOException, InvalidSDKKeyException {
String body = response.body().string();

if (!response.isSuccessful()) {
if (response.code() == 401) {
logger.error("[401] Invalid SDK key when accessing URI: " + request.url());
throw new InvalidSDKKeyException();
}
throw new IOException("Unexpected response when retrieving Feature Flag(s): " + response + " using url: "
+ request.url() + " with body: " + body);
throw new HttpErrorException(response.code());
}
logger.debug("Get flag(s) response: " + response.toString() + " with body: " + body);
logger.debug("Network response: " + response.networkResponse());
Expand All @@ -98,10 +93,4 @@ private String get(String path) throws IOException, InvalidSDKKeyException {
return body;
}
}

@SuppressWarnings("serial")
public static class InvalidSDKKeyException extends Exception {
public InvalidSDKKeyException() {
}
}
}
15 changes: 15 additions & 0 deletions src/main/java/com/launchdarkly/client/HttpErrorException.java
Original file line number Diff line number Diff line change
@@ -0,0 +1,15 @@
package com.launchdarkly.client;

@SuppressWarnings("serial")
class HttpErrorException extends Exception {
private final int status;

public HttpErrorException(int status) {
super("HTTP error " + status);
this.status = status;
}

public int getStatus() {
return status;
}
}
3 changes: 3 additions & 0 deletions src/main/java/com/launchdarkly/client/LDClient.java
Original file line number Diff line number Diff line change
Expand Up @@ -97,6 +97,9 @@ public LDClient(String sdkKey, LDConfig config) {
} catch (Exception e) {
logger.error("Exception encountered waiting for LaunchDarkly client initialization", e);
}
if (!updateProcessor.initialized()) {
logger.warn("LaunchDarkly client was not successfully initialized");
}
}
}

Expand Down
2 changes: 1 addition & 1 deletion src/main/java/com/launchdarkly/client/LDConfig.java
Original file line number Diff line number Diff line change
Expand Up @@ -113,7 +113,7 @@ protected LDConfig(Builder builder) {
.connectTimeout(connectTimeoutMillis, TimeUnit.MILLISECONDS)
.readTimeout(socketTimeoutMillis, TimeUnit.MILLISECONDS)
.writeTimeout(socketTimeoutMillis, TimeUnit.MILLISECONDS)
.retryOnConnectionFailure(true);
.retryOnConnectionFailure(false); // we will implement our own retry logic

// When streaming is enabled, http GETs made by FeatureRequester will
// always guarantee a new flag state. So, disable http response caching
Expand Down
44 changes: 32 additions & 12 deletions src/main/java/com/launchdarkly/client/LDUser.java
Original file line number Diff line number Diff line change
Expand Up @@ -608,11 +608,7 @@ public Builder privateEmail(String email) {
* @return the builder
*/
public Builder custom(String k, String v) {
checkCustomAttribute(k);
if (k != null && v != null) {
custom.put(k, new JsonPrimitive(v));
}
return this;
return custom(k, v == null ? null : new JsonPrimitive(v));
}

/**
Expand All @@ -625,11 +621,7 @@ public Builder custom(String k, String v) {
* @return the builder
*/
public Builder custom(String k, Number n) {
checkCustomAttribute(k);
if (k != null && n != null) {
custom.put(k, new JsonPrimitive(n));
}
return this;
return custom(k, n == null ? null : new JsonPrimitive(n));
}

/**
Expand All @@ -642,9 +634,22 @@ public Builder custom(String k, Number n) {
* @return the builder
*/
public Builder custom(String k, Boolean b) {
return custom(k, b == null ? null : new JsonPrimitive(b));
}

/**
* Add a custom attribute whose value can be any JSON type. When set to one of the
* <a href="http://docs.launchdarkly.com/docs/targeting-users#targeting-based-on-user-attributes">built-in
* user attribute keys</a>, this custom attribute will be ignored.
*
* @param k the key for the custom attribute
* @param v the value for the custom attribute
* @return the builder
*/
public Builder custom(String k, JsonElement v) {
checkCustomAttribute(k);
if (k != null && b != null) {
custom.put(k, new JsonPrimitive(b));
if (k != null && v != null) {
custom.put(k, v);
}
return this;
}
Expand Down Expand Up @@ -757,6 +762,21 @@ public Builder privateCustom(String k, Boolean b) {
return custom(k, b);
}

/**
* Add a custom attribute of any JSON type, that will not be sent back to LaunchDarkly.
* When set to one of the
* <a href="http://docs.launchdarkly.com/docs/targeting-users#targeting-based-on-user-attributes">built-in
* user attribute keys</a>, this custom attribute will be ignored.
*
* @param k the key for the custom attribute
* @param v the value for the custom attribute
* @return the builder
*/
public Builder privateCustom(String k, JsonElement v) {
privateAttrNames.add(k);
return custom(k, v);
}

/**
* Add a list of {@link java.lang.String}-valued custom attributes. When set to one of the
* <a href="http://docs.launchdarkly.com/docs/targeting-users#targeting-based-on-user-attributes">
Expand Down
19 changes: 15 additions & 4 deletions src/main/java/com/launchdarkly/client/PollingProcessor.java
Original file line number Diff line number Diff line change
Expand Up @@ -2,13 +2,21 @@

import com.google.common.util.concurrent.SettableFuture;
import com.google.common.util.concurrent.ThreadFactoryBuilder;

import org.slf4j.Logger;
import org.slf4j.LoggerFactory;

import java.io.IOException;
import java.util.concurrent.*;
import java.util.concurrent.Executors;
import java.util.concurrent.Future;
import java.util.concurrent.ScheduledExecutorService;
import java.util.concurrent.ThreadFactory;
import java.util.concurrent.TimeUnit;
import java.util.concurrent.atomic.AtomicBoolean;

import static com.launchdarkly.client.Util.httpErrorMessage;
import static com.launchdarkly.client.Util.isHttpErrorRecoverable;

class PollingProcessor implements UpdateProcessor {
private static final Logger logger = LoggerFactory.getLogger(PollingProcessor.class);

Expand Down Expand Up @@ -55,9 +63,12 @@ public void run() {
logger.info("Initialized LaunchDarkly client.");
initFuture.set(null);
}
} catch (FeatureRequestor.InvalidSDKKeyException e) {
logger.error("Received 401 error, no further polling requests will be made since SDK key is invalid");
scheduler.shutdown();
} catch (HttpErrorException e) {
logger.error(httpErrorMessage(e.getStatus(), "polling request", "will retry"));
if (!isHttpErrorRecoverable(e.getStatus())) {
scheduler.shutdown();
initFuture.set(null); // if client is initializing, make it stop waiting; has no effect if already inited
}
} catch (IOException e) {
logger.error("Encountered exception in LaunchDarkly client when retrieving update", e);
}
Expand Down
Loading

0 comments on commit 5bb05d4

Please sign in to comment.