Skip to content

Commit da3b24f

Browse files
mnoman09mikeproeng37
authored andcommitted
fix: Removed filtering of unknown attributes and assignment of empty map to null attributes and null event tags. (#221)
1 parent 61f6aad commit da3b24f

File tree

7 files changed

+114
-150
lines changed

7 files changed

+114
-150
lines changed

core-api/src/main/java/com/optimizely/ab/Optimizely.java

Lines changed: 30 additions & 71 deletions
Original file line numberDiff line numberDiff line change
@@ -209,18 +209,15 @@ Variation activate(@Nonnull ProjectConfig projectConfig,
209209
logger.info("Not activating user \"{}\" for experiment \"{}\".", userId, experiment.getKey());
210210
return null;
211211
}
212-
// determine whether all the given attributes are present in the project config. If not, filter out the unknown
213-
// attributes.
214-
Map<String, ?> filteredAttributes = filterAttributes(projectConfig, attributes);
215-
212+
Map<String, ?> copiedAttributes = copyAttributes(attributes);
216213
// bucket the user to the given experiment and dispatch an impression event
217-
Variation variation = decisionService.getVariation(experiment, userId, filteredAttributes);
214+
Variation variation = decisionService.getVariation(experiment, userId, copiedAttributes);
218215
if (variation == null) {
219216
logger.info("Not activating user \"{}\" for experiment \"{}\".", userId, experiment.getKey());
220217
return null;
221218
}
222219

223-
sendImpression(projectConfig, experiment, userId, filteredAttributes, variation);
220+
sendImpression(projectConfig, experiment, userId, copiedAttributes, variation);
224221

225222
return variation;
226223
}
@@ -292,6 +289,7 @@ public void track(@Nonnull String eventName,
292289
}
293290

294291
ProjectConfig currentConfig = getProjectConfig();
292+
Map<String, ?> copiedAttributes = copyAttributes(attributes);
295293

296294
EventType eventType = currentConfig.getEventTypeForName(eventName, errorHandler);
297295
if (eventType == null) {
@@ -300,20 +298,15 @@ public void track(@Nonnull String eventName,
300298
return;
301299
}
302300

303-
// determine whether all the given attributes are present in the project config. If not, filter out the unknown
304-
// attributes.
305-
Map<String, ?> filteredAttributes = filterAttributes(currentConfig, attributes);
306-
307301
if (eventTags == null) {
308302
logger.warn("Event tags is null when non-null was expected. Defaulting to an empty event tags map.");
309-
eventTags = Collections.<String, String>emptyMap();
310303
}
311304

312305
List<Experiment> experimentsForEvent = projectConfig.getExperimentsForEventKey(eventName);
313306
Map<Experiment, Variation> experimentVariationMap = new HashMap<Experiment, Variation>(experimentsForEvent.size());
314307
for (Experiment experiment : experimentsForEvent) {
315308
if (experiment.isRunning()) {
316-
Variation variation = decisionService.getVariation(experiment, userId, filteredAttributes);
309+
Variation variation = decisionService.getVariation(experiment, userId, copiedAttributes);
317310
if (variation != null) {
318311
experimentVariationMap.put(experiment, variation);
319312
}
@@ -331,7 +324,7 @@ public void track(@Nonnull String eventName,
331324
userId,
332325
eventType.getId(),
333326
eventType.getKey(),
334-
filteredAttributes,
327+
copiedAttributes,
335328
eventTags);
336329

337330
if (conversionEvent == null) {
@@ -354,7 +347,7 @@ public void track(@Nonnull String eventName,
354347
}
355348

356349
notificationCenter.sendNotifications(NotificationCenter.NotificationType.Track, eventName, userId,
357-
filteredAttributes, eventTags, conversionEvent);
350+
copiedAttributes, eventTags, conversionEvent);
358351
}
359352

360353
//======== FeatureFlag APIs ========//
@@ -406,17 +399,15 @@ else if (userId == null) {
406399
logger.info("No feature flag was found for key \"{}\".", featureKey);
407400
return false;
408401
}
409-
410-
Map<String, ?> filteredAttributes = filterAttributes(projectConfig, attributes);
411-
412-
FeatureDecision featureDecision = decisionService.getVariationForFeature(featureFlag, userId, filteredAttributes);
402+
Map<String, ?> copiedAttributes = copyAttributes(attributes);
403+
FeatureDecision featureDecision = decisionService.getVariationForFeature(featureFlag, userId, copiedAttributes);
413404
if (featureDecision.variation != null) {
414405
if (featureDecision.decisionSource.equals(FeatureDecision.DecisionSource.EXPERIMENT)) {
415406
sendImpression(
416407
projectConfig,
417408
featureDecision.experiment,
418409
userId,
419-
filteredAttributes,
410+
copiedAttributes,
420411
featureDecision.variation);
421412
} else {
422413
logger.info("The user \"{}\" is not included in an experiment for feature \"{}\".",
@@ -655,8 +646,9 @@ else if (userId == null) {
655646
}
656647

657648
String variableValue = variable.getDefaultValue();
649+
Map<String, ?> copiedAttributes = copyAttributes(attributes);
658650

659-
FeatureDecision featureDecision = decisionService.getVariationForFeature(featureFlag, userId, attributes);
651+
FeatureDecision featureDecision = decisionService.getVariationForFeature(featureFlag, userId, copiedAttributes);
660652
if (featureDecision.variation != null) {
661653
LiveVariableUsageInstance liveVariableUsageInstance =
662654
featureDecision.variation.getVariableIdToLiveVariableUsageInstanceMap().get(variable.getId());
@@ -716,10 +708,9 @@ Variation getVariation(@Nonnull Experiment experiment,
716708
Variation getVariation(@Nonnull Experiment experiment,
717709
@Nonnull String userId,
718710
@Nonnull Map<String, ?> attributes) throws UnknownExperimentException {
711+
Map<String, ?> copiedAttributes = copyAttributes(attributes);
719712

720-
Map<String, ?> filteredAttributes = filterAttributes(projectConfig, attributes);
721-
722-
return decisionService.getVariation(experiment, userId, filteredAttributes);
713+
return decisionService.getVariation(experiment, userId, copiedAttributes);
723714
}
724715

725716
public @Nullable
@@ -754,10 +745,8 @@ Variation getVariation(@Nonnull String experimentKey,
754745
// if we're unable to retrieve the associated experiment, return null
755746
return null;
756747
}
757-
758-
Map<String, ?> filteredAttributes = filterAttributes(projectConfig, attributes);
759-
760-
return decisionService.getVariation(experiment,userId,filteredAttributes);
748+
Map<String, ?> copiedAttributes = copyAttributes(attributes);
749+
return decisionService.getVariation(experiment, userId, copiedAttributes);
761750
}
762751

763752
/**
@@ -817,50 +806,6 @@ public UserProfileService getUserProfileService() {
817806
}
818807

819808
//======== Helper methods ========//
820-
821-
/**
822-
* Helper method to verify that the given attributes map contains only keys that are present in the
823-
* {@link ProjectConfig}.
824-
*
825-
* @param projectConfig the current project config
826-
* @param attributes the attributes map to validate and potentially filter. Attributes which starts with reserved key
827-
* {@link ProjectConfig#RESERVED_ATTRIBUTE_PREFIX} are kept.
828-
* @return the filtered attributes map (containing only attributes that are present in the project config) or an
829-
* empty map if a null attributes object is passed in
830-
*/
831-
private Map<String, ?> filterAttributes(@Nonnull ProjectConfig projectConfig,
832-
@Nonnull Map<String, ?> attributes) {
833-
if (attributes == null) {
834-
logger.warn("Attributes is null when non-null was expected. Defaulting to an empty attributes map.");
835-
return Collections.<String, String>emptyMap();
836-
}
837-
838-
// List of attribute keys
839-
List<String> unknownAttributes = null;
840-
841-
Map<String, Attribute> attributeKeyMapping = projectConfig.getAttributeKeyMapping();
842-
for (Map.Entry<String, ?> attribute : attributes.entrySet()) {
843-
if (!attributeKeyMapping.containsKey(attribute.getKey()) &&
844-
!attribute.getKey().startsWith(ProjectConfig.RESERVED_ATTRIBUTE_PREFIX)) {
845-
if (unknownAttributes == null) {
846-
unknownAttributes = new ArrayList<String>();
847-
}
848-
unknownAttributes.add(attribute.getKey());
849-
}
850-
}
851-
852-
if (unknownAttributes != null) {
853-
logger.warn("Attribute(s) {} not in the datafile.", unknownAttributes);
854-
// make a copy of the passed through attributes, then remove the unknown list
855-
attributes = new HashMap<>(attributes);
856-
for (String unknownAttribute : unknownAttributes) {
857-
attributes.remove(unknownAttribute);
858-
}
859-
}
860-
861-
return attributes;
862-
}
863-
864809
/**
865810
* Helper function to check that the provided userId is valid
866811
*
@@ -880,6 +825,20 @@ private boolean validateUserId(String userId) {
880825
return true;
881826
}
882827

828+
/**
829+
* Helper function to check that the provided attributes are null if not than it returns a copy
830+
*
831+
* @param attributes the attributes map being validated
832+
* @return copy of attributes
833+
*/
834+
private Map<String, ?> copyAttributes(Map<String, ?> attributes) {
835+
Map<String, ?> copiedAttributes = null;
836+
if (attributes != null) {
837+
copiedAttributes = new HashMap<>(attributes);
838+
}
839+
return copiedAttributes;
840+
}
841+
883842
//======== Builder ========//
884843

885844
public static Builder builder(@Nonnull String datafile,

core-api/src/main/java/com/optimizely/ab/config/audience/UserAttribute.java

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -24,6 +24,7 @@
2424
import javax.annotation.Nonnull;
2525
import javax.annotation.Nullable;
2626
import javax.annotation.concurrent.Immutable;
27+
import java.util.Collections;
2728
import java.util.Map;
2829

2930
/**
@@ -66,6 +67,9 @@ public Object getValue() {
6667
}
6768

6869
public @Nullable Boolean evaluate(Map<String, ?> attributes) {
70+
if (attributes == null) {
71+
attributes = Collections.emptyMap();
72+
}
6973
// Valid for primitive types, but needs to change when a value is an object or an array
7074
Object userAttributeValue = attributes.get(name);
7175

core-api/src/main/java/com/optimizely/ab/event/internal/EventFactory.java

Lines changed: 33 additions & 30 deletions
Original file line numberDiff line numberDiff line change
@@ -167,38 +167,41 @@ public LogEvent createConversionEvent(@Nonnull ProjectConfig projectConfig,
167167
private List<Attribute> buildAttributeList(ProjectConfig projectConfig, Map<String, ?> attributes) {
168168
List<Attribute> attributesList = new ArrayList<Attribute>();
169169

170-
for (Map.Entry<String, ?> entry : attributes.entrySet()) {
170+
if (attributes != null) {
171+
for (Map.Entry<String, ?> entry : attributes.entrySet()) {
172+
173+
// Ignore attributes with empty key
174+
if (entry.getKey().isEmpty()) {
175+
continue;
176+
}
177+
178+
// Filter down to the types of values we're allowed to track.
179+
// Don't allow Longs, BigIntegers, or BigDecimals - they /can/ theoretically be serialized as JSON numbers
180+
// but may take on values that can't be faithfully parsed by the backend.
181+
// https://developers.optimizely.com/x/events/api/#Attribute
182+
if (entry.getValue() == null ||
183+
!((entry.getValue() instanceof String) ||
184+
(entry.getValue() instanceof Integer) ||
185+
(entry.getValue() instanceof Double) ||
186+
(entry.getValue() instanceof Boolean))) {
187+
continue;
188+
}
189+
190+
String attributeId = projectConfig.getAttributeId(projectConfig, entry.getKey());
191+
if (attributeId == null) {
192+
continue;
193+
}
194+
195+
Attribute attribute = new Attribute.Builder()
196+
.setEntityId(attributeId)
197+
.setKey(entry.getKey())
198+
.setType(Attribute.CUSTOM_ATTRIBUTE_TYPE)
199+
.setValue(entry.getValue())
200+
.build();
201+
202+
attributesList.add(attribute);
171203

172-
//Ignore attributes with empty key
173-
if (entry.getKey().isEmpty()) {
174-
continue;
175204
}
176-
177-
// Filter down to the types of values we're allowed to track.
178-
// Don't allow Longs, BigIntegers, or BigDecimals - they /can/ theoretically be serialized as JSON numbers
179-
// but may take on values that can't be faithfully parsed by the backend.
180-
// https://developers.optimizely.com/x/events/api/#Attribute
181-
if (entry.getValue() == null ||
182-
!((entry.getValue() instanceof String) ||
183-
(entry.getValue() instanceof Integer) ||
184-
(entry.getValue() instanceof Double) ||
185-
(entry.getValue() instanceof Boolean))) {
186-
continue;
187-
}
188-
189-
String attributeId = projectConfig.getAttributeId(projectConfig, entry.getKey());
190-
if(attributeId == null) {
191-
continue;
192-
}
193-
194-
Attribute attribute = new Attribute.Builder()
195-
.setEntityId(attributeId)
196-
.setKey(entry.getKey())
197-
.setType(Attribute.CUSTOM_ATTRIBUTE_TYPE)
198-
.setValue(entry.getValue())
199-
.build();
200-
201-
attributesList.add(attribute);
202205
}
203206

204207
//checks if botFiltering value is not set in the project config file.

core-api/src/main/java/com/optimizely/ab/internal/EventTagUtils.java

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,6 @@
11
/**
22
*
3-
* Copyright 2016-2017, Optimizely and contributors
3+
* Copyright 2016-2018, Optimizely and contributors
44
*
55
* Licensed under the Apache License, Version 2.0 (the "License");
66
* you may not use this file except in compliance with the License.
@@ -33,7 +33,7 @@ public final class EventTagUtils {
3333
*/
3434
public static Long getRevenueValue(@Nonnull Map<String, ?> eventTags) {
3535
Long eventValue = null;
36-
if (eventTags.containsKey(ReservedEventKey.REVENUE.toString())) {
36+
if (eventTags != null && eventTags.containsKey(ReservedEventKey.REVENUE.toString())) {
3737
Object rawValue = eventTags.get(ReservedEventKey.REVENUE.toString());
3838
if (Long.class.isInstance(rawValue)) {
3939
eventValue = (Long)rawValue;
@@ -53,7 +53,7 @@ public static Long getRevenueValue(@Nonnull Map<String, ?> eventTags) {
5353
*/
5454
public static Double getNumericValue(@Nonnull Map<String, ?> eventTags) {
5555
Double eventValue = null;
56-
if (eventTags.containsKey(ReservedEventKey.VALUE.toString())) {
56+
if (eventTags != null && eventTags.containsKey(ReservedEventKey.VALUE.toString())) {
5757
Object rawValue = eventTags.get(ReservedEventKey.VALUE.toString());
5858
if (rawValue instanceof Number) {
5959
eventValue = ((Number) rawValue).doubleValue();

core-api/src/main/java/com/optimizely/ab/notification/ActivateNotificationListener.java

Lines changed: 5 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -37,8 +37,11 @@ public final void notify(Object... args) {
3737
Experiment experiment = (Experiment) args[0];
3838
assert(args[1] instanceof String);
3939
String userId = (String) args[1];
40-
assert(args[2] instanceof java.util.Map);
41-
Map<String, ?> attributes = (Map<String, ?>) args[2];
40+
Map<String, ?> attributes = null;
41+
if (args[2] != null) {
42+
assert (args[2] instanceof java.util.Map);
43+
attributes = (Map<String, ?>) args[2];
44+
}
4245
assert(args[3] instanceof Variation);
4346
Variation variation = (Variation) args[3];
4447
assert(args[4] instanceof LogEvent);

core-api/src/main/java/com/optimizely/ab/notification/TrackNotificationListener.java

Lines changed: 10 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -35,10 +35,16 @@ public final void notify(Object... args) {
3535
String eventKey = (String) args[0];
3636
assert(args[1] instanceof String);
3737
String userId = (String) args[1];
38-
assert(args[2] instanceof java.util.Map);
39-
Map<String, ?> attributes = (Map<String, ?>) args[2];
40-
assert(args[3] instanceof java.util.Map);
41-
Map<String, ?> eventTags = (Map<String, ?>) args[3];
38+
Map<String, ?> attributes = null;
39+
if (args[2] != null) {
40+
assert (args[2] instanceof java.util.Map);
41+
attributes = (Map<String, ?>) args[2];
42+
}
43+
Map<String, ?> eventTags = null;
44+
if (args[3] != null) {
45+
assert (args[3] instanceof java.util.Map);
46+
eventTags = (Map<String, ?>) args[3];
47+
}
4248
assert(args[4] instanceof LogEvent);
4349
LogEvent logEvent = (LogEvent) args[4];
4450

0 commit comments

Comments
 (0)