Skip to content

Commit c54870e

Browse files
authored
Releases/v0.7.0 beta (#6)
* Moving participant init to client init * Changing participant builder pattern * Audience filtering on user attributes plus tests * Adding uid to allocation store methods * Changing back dependencies * Fixing typo * Making sure all allocations are searched * Changing log level of AscendKeyError * Rewording changelog * Updating README * Changing date in changelog * Per Tinou's comment; implementing cache with LinkedHashMap * Fixing cache bug * Accidentally renamed public method
1 parent 8c9b4fe commit c54870e

35 files changed

+1328
-360
lines changed

.gitignore

+2-1
Original file line numberDiff line numberDiff line change
@@ -6,4 +6,5 @@
66
/build
77
/captures
88
.externalNativeBuild
9-
/out
9+
/out
10+
/scratch

CHANGELOG.md

+12-1
Original file line numberDiff line numberDiff line change
@@ -3,6 +3,17 @@
33

44
All updates that affect the released version of the Ascend Java Client will be detailed here. This project adheres to [Semantic Versioning](http://semver.org).
55

6-
## [0.6.0] - 2019-04-16
6+
## [0.7.0-beta] - 2019-05-29
7+
### Fixed
8+
- Participant's now initialize with the client instead of getting reused like before.
9+
- Stack traces are now logged upon exceptions.
10+
### Deprecated
11+
- Deprecating setAscendParticipant method in the AscendConfig builder in favor of passing the participant into the
12+
AscendClient init method.
13+
### Added
14+
- Added audience filters, allowing for filtering of the participant through user attributes.
15+
- AllocationStore now requires a unique user id to be given with the allocation to be stored.
16+
17+
## [0.6.0-beta] - 2019-04-16
718
### Added
819
- Deprecating the implementations AsyncHttpClientImpl and OkHttpClientImpl in favor of the implementations AsyncHttpClient and OkHttpClient. The new implementations provide more options when constructing an HttpClient.

README.md

+1-1
Original file line numberDiff line numberDiff line change
@@ -15,7 +15,7 @@
1515
<dependency>
1616
<groupId>ai.evolv</groupId>
1717
<artifactId>ascend-sdk</artifactId>
18-
<version>0.5.1</version>
18+
<version>0.7.0</version>
1919
</dependency>
2020
```
2121

build.gradle

+1-1
Original file line numberDiff line numberDiff line change
@@ -7,7 +7,7 @@ plugins {
77

88
group = "ai.evolv"
99
archivesBaseName = "ascend-sdk"
10-
version = "0.6.0"
10+
version = "0.7.0"
1111

1212
sourceCompatibility = 1.8
1313

src/main/java/ai/evolv/Allocations.java

+43-20
Original file line numberDiff line numberDiff line change
@@ -10,45 +10,68 @@
1010
import java.util.ArrayList;
1111
import java.util.Arrays;
1212
import java.util.HashSet;
13-
import java.util.Map;
13+
import java.util.List;
1414
import java.util.Set;
1515

16+
import org.slf4j.Logger;
17+
import org.slf4j.LoggerFactory;
18+
1619
class Allocations {
1720

21+
private static final Logger LOGGER = LoggerFactory.getLogger(Allocations.class);
22+
1823
private final JsonArray allocations;
1924

25+
private final Audience audience = new Audience();
26+
2027
Allocations(JsonArray allocations) {
2128
this.allocations = allocations;
2229
}
2330

24-
<T> T getValueFromGenome(String key, Class<T> cls) throws AscendKeyError {
31+
<T> T getValueFromAllocations(String key, Class<T> cls, AscendParticipant participant)
32+
throws AscendKeyError {
2533
ArrayList<String> keyParts = new ArrayList<>(Arrays.asList(key.split("\\.")));
26-
JsonElement value = getGenomeFromAllocations().get("genome");
27-
for (String part : keyParts) {
28-
JsonObject jsonObject = value.getAsJsonObject();
29-
value = jsonObject.get(part);
34+
if (keyParts.isEmpty()) {
35+
throw new AscendKeyError("Key provided was empty.");
36+
}
3037

31-
if (value == null) {
32-
throw new AscendKeyError("Could not find value for key:" + key);
38+
for (JsonElement a : allocations) {
39+
JsonObject allocation = a.getAsJsonObject();
40+
if (!audience.filter(participant.getUserAttributes(), allocation)) {
41+
try {
42+
JsonElement element = getElementFromGenome(allocation.get("genome"), keyParts);
43+
return new Gson().fromJson(element, cls);
44+
} catch (AscendKeyError e) {
45+
LOGGER.debug(String.format("Unable to find key %s in experiment %s.",
46+
keyParts.toString(), allocation.get("eid").getAsString()), e);
47+
continue;
48+
}
3349
}
50+
51+
LOGGER.debug(String.format("Participant was filtered from experiment %s",
52+
allocation.get("eid").getAsString()));
3453
}
3554

36-
Gson gson = new Gson();
37-
return gson.fromJson(value, cls);
55+
throw new AscendKeyError(String.format("No value was found in any allocations for key: %s",
56+
keyParts.toString()));
3857
}
3958

40-
JsonObject getGenomeFromAllocations() {
41-
JsonObject genome = new JsonObject();
42-
for (JsonElement allocation : allocations) {
43-
JsonObject originalGenome = allocation.getAsJsonObject().getAsJsonObject("genome");
44-
Set<Map.Entry<String, JsonElement>> entrySet = originalGenome.entrySet();
45-
for (Map.Entry<String, JsonElement> entry : entrySet) {
46-
genome.add(entry.getKey(), originalGenome.get(entry.getKey()));
59+
private JsonElement getElementFromGenome(JsonElement genome, List<String> keyParts)
60+
throws AscendKeyError {
61+
JsonElement element = genome;
62+
if (element == null) {
63+
throw new AscendKeyError("Allocation genome was empty.");
64+
}
65+
66+
for (String part : keyParts) {
67+
JsonObject object = element.getAsJsonObject();
68+
element = object.get(part);
69+
if (element == null) {
70+
throw new AscendKeyError("Could not find value for key: " + keyParts.toString());
4771
}
4872
}
49-
JsonObject genomeWrapped = new JsonObject();
50-
genomeWrapped.add("genome", genome);
51-
return genomeWrapped;
73+
74+
return element;
5275
}
5376

5477
/**

src/main/java/ai/evolv/Allocator.java

+23-45
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,5 @@
11
package ai.evolv;
22

3-
import ai.evolv.exceptions.AscendRuntimeException;
4-
53
import com.google.gson.JsonArray;
64
import com.google.gson.JsonParser;
75

@@ -15,7 +13,7 @@
1513

1614
class Allocator {
1715

18-
private static Logger logger = LoggerFactory.getLogger(Allocator.class);
16+
private static final Logger LOGGER = LoggerFactory.getLogger(Allocator.class);
1917

2018
enum AllocationStatus {
2119
FETCHING, RETRIEVED, FAILED
@@ -24,7 +22,7 @@ enum AllocationStatus {
2422
private final ExecutionQueue executionQueue;
2523
private final AscendAllocationStore store;
2624
private final AscendConfig config;
27-
private final AscendParticipant ascendParticipant;
25+
private final AscendParticipant participant;
2826
private final EventEmitter eventEmitter;
2927
private final HttpClient httpClient;
3028

@@ -33,14 +31,14 @@ enum AllocationStatus {
3331

3432
private AllocationStatus allocationStatus;
3533

36-
Allocator(AscendConfig config) {
34+
Allocator(AscendConfig config, AscendParticipant participant) {
3735
this.executionQueue = config.getExecutionQueue();
3836
this.store = config.getAscendAllocationStore();
3937
this.config = config;
40-
this.ascendParticipant = config.getAscendParticipant();
38+
this.participant = participant;
4139
this.httpClient = config.getHttpClient();
4240
this.allocationStatus = AllocationStatus.FETCHING;
43-
this.eventEmitter = new EventEmitter(config);
41+
this.eventEmitter = new EventEmitter(config, participant);
4442

4543
}
4644

@@ -61,14 +59,14 @@ String createAllocationsUrl() {
6159
String path = String.format("//%s/%s/%s/allocations", config.getDomain(),
6260
config.getVersion(),
6361
config.getEnvironmentId());
64-
String queryString = String.format("uid=%s&sid=%s", ascendParticipant.getUserId(),
65-
ascendParticipant.getSessionId());
62+
String queryString = String.format("uid=%s&sid=%s", participant.getUserId(),
63+
participant.getSessionId());
6664
URI uri = new URI(config.getHttpScheme(), null, path, queryString, null);
6765
URL url = uri.toURL();
6866

6967
return url.toString();
7068
} catch (Exception e) {
71-
logger.error(e.getMessage());
69+
LOGGER.error("There was an issue creating the allocations url.", e);
7270
return "";
7371
}
7472
}
@@ -80,12 +78,12 @@ CompletableFuture<JsonArray> fetchAllocations() {
8078
JsonParser parser = new JsonParser();
8179
JsonArray allocations = parser.parse(responseBody).getAsJsonArray();
8280

83-
JsonArray previousAllocations = store.get();
81+
JsonArray previousAllocations = store.get(participant.getUserId());
8482
if (allocationsNotEmpty(previousAllocations)) {
8583
allocations = Allocations.reconcileAllocations(previousAllocations, allocations);
8684
}
8785

88-
store.put(allocations);
86+
store.put(participant.getUserId(), allocations);
8987
allocationStatus = AllocationStatus.RETRIEVED;
9088

9189
if (confirmationSandbagged) {
@@ -96,60 +94,40 @@ CompletableFuture<JsonArray> fetchAllocations() {
9694
eventEmitter.contaminate(allocations);
9795
}
9896

99-
// could throw an exception due to customer's action logic
100-
try {
101-
executionQueue.executeAllWithValuesFromAllocations(allocations);
102-
} catch (Exception e) {
103-
throw new AscendRuntimeException(e);
104-
}
97+
executionQueue.executeAllWithValuesFromAllocations(allocations);
10598

10699
return allocations;
107-
}).handle((result, ex) -> {
108-
if (ex != null && ex.getCause() instanceof AscendRuntimeException) {
109-
// surface any customer implementation errors
110-
logger.error(ex.getCause().getCause().toString());
111-
return result;
112-
} else if (ex != null && result == null) {
113-
return resolveAllocationFailure();
114-
} else {
115-
return result;
116-
}
100+
}).exceptionally(e -> {
101+
LOGGER.error("There was an exception while retrieving allocations.", e);
102+
return resolveAllocationFailure();
117103
});
118104
}
119105

120106
JsonArray resolveAllocationFailure() {
121-
logger.warn("There was an error while making an allocation request.");
122-
123-
JsonArray allocations = store.get();
124-
if (allocationsNotEmpty(allocations)) {
125-
logger.warn("Falling back to participant's previous allocation.");
126-
107+
JsonArray previousAllocations = store.get(participant.getUserId());
108+
if (allocationsNotEmpty(previousAllocations)) {
109+
LOGGER.debug("Falling back to participant's previous allocation.");
127110
if (confirmationSandbagged) {
128-
eventEmitter.confirm(allocations);
111+
eventEmitter.confirm(previousAllocations);
129112
}
130113

131114
if (contaminationSandbagged) {
132-
eventEmitter.contaminate(allocations);
115+
eventEmitter.contaminate(previousAllocations);
133116
}
134117

135118
allocationStatus = AllocationStatus.RETRIEVED;
136-
executionQueue.executeAllWithValuesFromAllocations(allocations);
119+
executionQueue.executeAllWithValuesFromAllocations(previousAllocations);
137120
} else {
138-
logger.warn("Falling back to the supplied defaults.");
139-
121+
LOGGER.debug("Falling back to the supplied defaults.");
140122
allocationStatus = AllocationStatus.FAILED;
141123
executionQueue.executeAllWithValuesFromDefaults();
142-
143-
allocations = new JsonArray();
124+
previousAllocations = new JsonArray();
144125
}
145126

146-
return allocations;
127+
return previousAllocations;
147128
}
148129

149130
static boolean allocationsNotEmpty(JsonArray allocations) {
150131
return allocations != null && allocations.size() > 0;
151132
}
152-
153-
154-
155133
}

src/main/java/ai/evolv/AscendAllocationStore.java

+6-4
Original file line numberDiff line numberDiff line change
@@ -10,17 +10,19 @@ public interface AscendAllocationStore {
1010
* Retrieves a JsonArray that represents the participant's allocations.
1111
* If there are no stored allocations, should return an empty JsonArray.
1212
* </p>
13-
* @return a participant's allocations
13+
* @param uid the participant's unique id
14+
* @return an allocation if one exists else an empty JsonArray
1415
*/
15-
JsonArray get();
16+
JsonArray get(String uid);
1617

1718
/**
1819
* Stores a JsonArray.
1920
* <p>
2021
* Stores the given JsonArray.
2122
* </p>
22-
* @param allocations a participant's allocations
23+
* @param uid the participant's unique id
24+
* @param allocations the participant's allocations
2325
*/
24-
void put(JsonArray allocations);
26+
void put(String uid, JsonArray allocations);
2527

2628
}

src/main/java/ai/evolv/AscendClientFactory.java

+33-12
Original file line numberDiff line numberDiff line change
@@ -13,29 +13,50 @@ public class AscendClientFactory {
1313

1414
/**
1515
* Creates instances of the AscendClient.
16+
*
1617
* @param config an instance of AscendConfig
1718
* @return an instance of AscendClient
1819
*/
1920
public static AscendClient init(AscendConfig config) {
20-
LOGGER.info("Initializing Ascend Client.");
21+
LOGGER.debug("Initializing Ascend Client.");
22+
AscendParticipant participant = config.getAscendParticipant();
23+
if (participant == null) {
24+
participant = AscendParticipant.builder().build();
25+
}
2126

22-
AscendAllocationStore store = config.getAscendAllocationStore();
23-
Allocator allocator = new Allocator(config);
27+
return AscendClientFactory.createClient(config, participant);
28+
}
29+
30+
/**
31+
* Creates instances of the AscendClient.
32+
*
33+
* @param config general configurations for the SDK
34+
* @param participant the participant for the initialized client
35+
* @return an instance of AscendClient
36+
*/
37+
public static AscendClient init(AscendConfig config, AscendParticipant participant) {
38+
LOGGER.debug("Initializing Ascend Client.");
39+
return AscendClientFactory.createClient(config, participant);
40+
}
2441

25-
JsonArray previousAllocations = store.get();
42+
private static AscendClient createClient(AscendConfig config, AscendParticipant participant) {
43+
AscendAllocationStore store = config.getAscendAllocationStore();
44+
JsonArray previousAllocations = store.get(participant.getUserId());
2645
boolean reconciliationNeeded = false;
2746
if (Allocator.allocationsNotEmpty(previousAllocations)) {
28-
String storedUserId = previousAllocations.get(0)
29-
.getAsJsonObject().get("uid")
30-
.getAsString();
31-
config.getAscendParticipant().setUserId(storedUserId);
3247
reconciliationNeeded = true;
3348
}
3449

35-
// fetch and reconcile allocations asynchronously
36-
CompletableFuture<JsonArray> fetchedAllocations = allocator.fetchAllocations();
50+
Allocator allocator = new Allocator(config, participant);
3751

38-
return new AscendClientImpl(config, new EventEmitter(config), fetchedAllocations,
39-
allocator, reconciliationNeeded);
52+
// fetch and reconcile allocations asynchronously
53+
CompletableFuture<JsonArray> futureAllocations = allocator.fetchAllocations();
54+
55+
return new AscendClientImpl(config,
56+
new EventEmitter(config, participant),
57+
futureAllocations,
58+
allocator,
59+
reconciliationNeeded,
60+
participant);
4061
}
4162
}

0 commit comments

Comments
 (0)