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

Commit

Permalink
prepare 4.11.0 release (#182)
Browse files Browse the repository at this point in the history
  • Loading branch information
eli-darkly authored Jan 16, 2020
1 parent b01f593 commit 5bbaa7b
Show file tree
Hide file tree
Showing 6 changed files with 93 additions and 18 deletions.
2 changes: 1 addition & 1 deletion CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -5,7 +5,7 @@ All notable changes to the LaunchDarkly Java SDK will be documented in this file

## [4.10.1] - 2020-01-06
### Fixed:
- The `pom.xml` dependencies were incorrectly specifying `runtime` scope rather than `compile`, causing problems for application that did not have their own dependencies on Gson and SLF4J. ([#151](https://github.com/launchdarkly/java-client/issues/151))
- The `pom.xml` dependencies were incorrectly specifying `runtime` scope rather than `compile`, causing problems for applications that did not have their own dependencies on Gson and SLF4J. ([#151](https://github.com/launchdarkly/java-client/issues/151))

## [4.10.0] - 2019-12-13
### Added:
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -15,6 +15,7 @@
import java.util.Date;
import java.util.List;
import java.util.Random;
import java.util.UUID;
import java.util.concurrent.ArrayBlockingQueue;
import java.util.concurrent.BlockingQueue;
import java.util.concurrent.Executors;
Expand Down Expand Up @@ -42,6 +43,7 @@ final class DefaultEventProcessor implements EventProcessor {
private static final Logger logger = LoggerFactory.getLogger(DefaultEventProcessor.class);
private static final String EVENT_SCHEMA_HEADER = "X-LaunchDarkly-Event-Schema";
private static final String EVENT_SCHEMA_VERSION = "3";
private static final String EVENT_PAYLOAD_ID_HEADER = "X-LaunchDarkly-Payload-ID";

private final BlockingQueue<EventProcessorMessage> inbox;
private final ScheduledExecutorService scheduler;
Expand Down Expand Up @@ -542,7 +544,8 @@ void stop() {

private void postEvents(String json, int outputEventCount) {
String uriStr = config.eventsURI.toString() + "/bulk";

String eventPayloadId = UUID.randomUUID().toString();

logger.debug("Posting {} event(s) to {} with payload: {}",
outputEventCount, uriStr, json);

Expand All @@ -558,6 +561,7 @@ private void postEvents(String json, int outputEventCount) {
.post(RequestBody.create(MediaType.parse("application/json; charset=utf-8"), json))
.addHeader("Content-Type", "application/json")
.addHeader(EVENT_SCHEMA_HEADER, EVENT_SCHEMA_VERSION)
.addHeader(EVENT_PAYLOAD_ID_HEADER, eventPayloadId)
.build();

long startTime = System.currentTimeMillis();
Expand Down
48 changes: 36 additions & 12 deletions src/main/java/com/launchdarkly/client/EvaluationReason.java
Original file line number Diff line number Diff line change
Expand Up @@ -78,18 +78,18 @@ public static enum ErrorKind {
WRONG_TYPE,
/**
* Indicates that an unexpected exception stopped flag evaluation. An error message will always be logged
* in this case.
* in this case, and the exception should be available via {@link EvaluationReason.Error#getException()}.
*/
EXCEPTION
}

// static instances to avoid repeatedly allocating reasons for the same errors
private static final Error ERROR_CLIENT_NOT_READY = new Error(ErrorKind.CLIENT_NOT_READY);
private static final Error ERROR_FLAG_NOT_FOUND = new Error(ErrorKind.FLAG_NOT_FOUND);
private static final Error ERROR_MALFORMED_FLAG = new Error(ErrorKind.MALFORMED_FLAG);
private static final Error ERROR_USER_NOT_SPECIFIED = new Error(ErrorKind.USER_NOT_SPECIFIED);
private static final Error ERROR_WRONG_TYPE = new Error(ErrorKind.WRONG_TYPE);
private static final Error ERROR_EXCEPTION = new Error(ErrorKind.EXCEPTION);
private static final Error ERROR_CLIENT_NOT_READY = new Error(ErrorKind.CLIENT_NOT_READY, null);
private static final Error ERROR_FLAG_NOT_FOUND = new Error(ErrorKind.FLAG_NOT_FOUND, null);
private static final Error ERROR_MALFORMED_FLAG = new Error(ErrorKind.MALFORMED_FLAG, null);
private static final Error ERROR_USER_NOT_SPECIFIED = new Error(ErrorKind.USER_NOT_SPECIFIED, null);
private static final Error ERROR_WRONG_TYPE = new Error(ErrorKind.WRONG_TYPE, null);
private static final Error ERROR_EXCEPTION = new Error(ErrorKind.EXCEPTION, null);

private final Kind kind;

Expand Down Expand Up @@ -168,9 +168,19 @@ public static Error error(ErrorKind errorKind) {
case MALFORMED_FLAG: return ERROR_MALFORMED_FLAG;
case USER_NOT_SPECIFIED: return ERROR_USER_NOT_SPECIFIED;
case WRONG_TYPE: return ERROR_WRONG_TYPE;
default: return new Error(errorKind);
default: return new Error(errorKind, null);
}
}

/**
* Returns an instance of {@link Error} with the kind {@link ErrorKind#EXCEPTION} and an exception instance.
* @param exception the exception that caused the error
* @return a reason object
* @since 4.11.0
*/
public static Error exception(Exception exception) {
return new Error(ErrorKind.EXCEPTION, exception);
}

/**
* Subclass of {@link EvaluationReason} that indicates that the flag was off and therefore returned
Expand Down Expand Up @@ -307,11 +317,13 @@ private Fallthrough()
*/
public static class Error extends EvaluationReason {
private final ErrorKind errorKind;
private final Exception exception;

private Error(ErrorKind errorKind) {
private Error(ErrorKind errorKind, Exception exception) {
super(Kind.ERROR);
checkNotNull(errorKind);
this.errorKind = errorKind;
this.exception = exception;
}

/**
Expand All @@ -322,19 +334,31 @@ public ErrorKind getErrorKind() {
return errorKind;
}

/**
* Returns the exception that caused the error condition, if applicable.
* <p>
* This is only set if {@link #getErrorKind()} is {@link ErrorKind#EXCEPTION}.
*
* @return the exception instance
* @since 4.11.0
*/
public Exception getException() {
return exception;
}

@Override
public boolean equals(Object other) {
return other instanceof Error && errorKind == ((Error) other).errorKind;
return other instanceof Error && errorKind == ((Error) other).errorKind && Objects.equals(exception, ((Error) other).exception);
}

@Override
public int hashCode() {
return errorKind.hashCode();
return Objects.hash(errorKind, exception);
}

@Override
public String toString() {
return getKind().name() + "(" + errorKind.name() + ")";
return getKind().name() + "(" + errorKind.name() + (exception == null ? "" : ("," + exception)) + ")";
}
}
}
4 changes: 2 additions & 2 deletions src/main/java/com/launchdarkly/client/LDClient.java
Original file line number Diff line number Diff line change
Expand Up @@ -203,7 +203,7 @@ public FeatureFlagsState allFlagsState(LDUser user, FlagsStateOption... options)
} catch (Exception e) {
logger.error("Exception caught for feature flag \"{}\" when evaluating all flags: {}", entry.getKey(), e.toString());
logger.debug(e.toString(), e);
builder.addFlag(entry.getValue(), EvaluationDetail.error(EvaluationReason.ErrorKind.EXCEPTION, LDValue.ofNull()));
builder.addFlag(entry.getValue(), EvaluationDetail.fromValue(LDValue.ofNull(), null, EvaluationReason.exception(e)));
}
}
return builder.build();
Expand Down Expand Up @@ -375,7 +375,7 @@ private EvaluationDetail<LDValue> evaluateInternal(String featureKey, LDUser use
sendFlagRequestEvent(eventFactory.newDefaultFeatureRequestEvent(featureFlag, user, defaultValue,
EvaluationReason.ErrorKind.EXCEPTION));
}
return EvaluationDetail.error(EvaluationReason.ErrorKind.EXCEPTION, defaultValue);
return EvaluationDetail.fromValue(defaultValue, null, EvaluationReason.exception(e));
}
}

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -11,6 +11,7 @@

import java.text.SimpleDateFormat;
import java.util.Date;
import java.util.UUID;
import java.util.concurrent.TimeUnit;

import static com.launchdarkly.client.TestHttpUtil.httpsServerWithSelfSignedCert;
Expand All @@ -22,6 +23,7 @@
import static org.hamcrest.Matchers.allOf;
import static org.hamcrest.Matchers.contains;
import static org.hamcrest.Matchers.equalTo;
import static org.hamcrest.Matchers.not;
import static org.hamcrest.Matchers.notNullValue;
import static org.hamcrest.Matchers.nullValue;
import static org.junit.Assert.assertEquals;
Expand Down Expand Up @@ -485,6 +487,50 @@ public void eventSchemaIsSent() throws Exception {
}
}

@Test
public void eventPayloadIdIsSent() throws Exception {
Event e = EventFactory.DEFAULT.newIdentifyEvent(user);

try (MockWebServer server = makeStartedServer(eventsSuccessResponse())) {
try (DefaultEventProcessor ep = new DefaultEventProcessor(SDK_KEY, baseConfig(server).build())) {
ep.sendEvent(e);
}

RecordedRequest req = server.takeRequest();
String payloadHeaderValue = req.getHeader("X-LaunchDarkly-Payload-ID");
assertThat(payloadHeaderValue, notNullValue(String.class));
assertThat(UUID.fromString(payloadHeaderValue), notNullValue(UUID.class));
}
}

@Test
public void eventPayloadIdReusedOnRetry() throws Exception {
MockResponse errorResponse = new MockResponse().setResponseCode(429);
Event e = EventFactory.DEFAULT.newIdentifyEvent(user);

try (MockWebServer server = makeStartedServer(errorResponse, eventsSuccessResponse(), eventsSuccessResponse())) {
try (DefaultEventProcessor ep = new DefaultEventProcessor(SDK_KEY, baseConfig(server).build())) {
ep.sendEvent(e);
ep.flush();
// Necessary to ensure the retry occurs before the second request for test assertion ordering
ep.waitUntilInactive();
ep.sendEvent(e);
}

// Failed response request
RecordedRequest req = server.takeRequest(0, TimeUnit.SECONDS);
String payloadId = req.getHeader("X-LaunchDarkly-Payload-ID");
// Retry request has same payload ID as failed request
req = server.takeRequest(0, TimeUnit.SECONDS);
String retryId = req.getHeader("X-LaunchDarkly-Payload-ID");
assertThat(retryId, equalTo(payloadId));
// Second request has different payload ID from first request
req = server.takeRequest(0, TimeUnit.SECONDS);
payloadId = req.getHeader("X-LaunchDarkly-Payload-ID");
assertThat(retryId, not(equalTo(payloadId)));
}
}

@Test
public void http400ErrorIsRecoverable() throws Exception {
testRecoverableHttpError(400);
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -261,15 +261,16 @@ public void appropriateErrorIfValueWrongType() throws Exception {

@Test
public void appropriateErrorForUnexpectedException() throws Exception {
FeatureStore badFeatureStore = featureStoreThatThrowsException(new RuntimeException("sorry"));
RuntimeException exception = new RuntimeException("sorry");
FeatureStore badFeatureStore = featureStoreThatThrowsException(exception);
LDConfig badConfig = new LDConfig.Builder()
.featureStoreFactory(specificFeatureStore(badFeatureStore))
.eventProcessorFactory(Components.nullEventProcessor())
.updateProcessorFactory(Components.nullUpdateProcessor())
.build();
try (LDClientInterface badClient = new LDClient("SDK_KEY", badConfig)) {
EvaluationDetail<Boolean> expectedResult = EvaluationDetail.fromValue(false, null,
EvaluationReason.error(EvaluationReason.ErrorKind.EXCEPTION));
EvaluationReason.exception(exception));
assertEquals(expectedResult, badClient.boolVariationDetail("key", user, false));
}
}
Expand Down

0 comments on commit 5bbaa7b

Please sign in to comment.