Skip to content

Commit

Permalink
Merge pull request #5 from absmartly/feature/custom-fields-support
Browse files Browse the repository at this point in the history
Add support for custom fields
  • Loading branch information
marcio-absmartly committed Nov 2, 2023
2 parents 8e4bd49 + b2d9598 commit b7fe53c
Show file tree
Hide file tree
Showing 8 changed files with 409 additions and 47 deletions.
137 changes: 107 additions & 30 deletions core-api/src/main/java/com/absmartly/sdk/Context.java
Original file line number Diff line number Diff line change
Expand Up @@ -173,6 +173,57 @@ public String[] getExperiments() {
}
}

public String[] getCustomFieldKeys() {
final Set<String> keys = new HashSet<String>();

try {
dataLock_.readLock().lock();
for (final Experiment experiment : data_.experiments) {
if (experiment.customFieldValues != null) {
for (final CustomFieldValue customFieldValue : experiment.customFieldValues) {
keys.add(customFieldValue.name);
}
}
}

return keys.toArray(new String[0]);
} finally {
dataLock_.readLock().unlock();
}
}

public Object getCustomFieldValue(@Nonnull final String experimentName, @Nonnull final String key) {
try {
dataLock_.readLock().lock();
final ContextExperiment experiment = index_.get(experimentName);
if (experiment != null) {
final ContextCustomFieldValue field = experiment.customFieldValues.get(key);
if (field != null) {
return field.value;
}
}
return null;
} finally {
dataLock_.readLock().unlock();
}
}

public Object getCustomFieldValueType(@Nonnull final String experimentName, @Nonnull final String key) {
try {
dataLock_.readLock().lock();
final ContextExperiment experiment = index_.get(experimentName);
if (experiment != null) {
final ContextCustomFieldValue field = experiment.customFieldValues.get(key);
if (field != null) {
return field.type;
}
}
return null;
} finally {
dataLock_.readLock().unlock();
}
}

public ContextData getData() {
checkReady(true);

Expand Down Expand Up @@ -369,12 +420,12 @@ public Map<String, List<String>> getVariableKeys() {

try {
dataLock_.readLock().lock();
for (Map.Entry<String, List<ExperimentVariables>> entry : indexVariables_.entrySet()) {
for (Map.Entry<String, List<ContextExperiment>> entry : indexVariables_.entrySet()) {
final String key = entry.getKey();
final List<ExperimentVariables> keyExperimentVariables = entry.getValue();
final List<ContextExperiment> keyExperimentVariables = entry.getValue();
final List<String> values = new ArrayList<String>(keyExperimentVariables.size());

for (final ExperimentVariables experimentVariables : keyExperimentVariables) {
for (final ContextExperiment experimentVariables : keyExperimentVariables) {
values.add(experimentVariables.data.name);
}
variableKeys.put(key, values);
Expand Down Expand Up @@ -679,7 +730,7 @@ private Assignment getAssignment(final String experimentName) {
if (assignment != null) {
final Integer custom = cassignments_.get(experimentName);
final Integer override = overrides_.get(experimentName);
final ExperimentVariables experiment = Context.this.getExperiment(experimentName);
final ContextExperiment experiment = Context.this.getExperiment(experimentName);

if (override != null) {
if (assignment.overridden && assignment.variant == override) {
Expand Down Expand Up @@ -709,7 +760,7 @@ private Assignment getAssignment(final String experimentName) {

final Integer custom = cassignments_.get(experimentName);
final Integer override = overrides_.get(experimentName);
final ExperimentVariables experiment = Context.this.getExperiment(experimentName);
final ContextExperiment experiment = Context.this.getExperiment(experimentName);

final Assignment assignment = new Assignment();
assignment.name = experimentName;
Expand Down Expand Up @@ -794,10 +845,10 @@ private Assignment getAssignment(final String experimentName) {
}

private Assignment getVariableAssignment(final String key) {
final List<ExperimentVariables> keyExperimentVariables = getVariableExperiments(key);
final List<ContextExperiment> keyExperimentVariables = getVariableExperiments(key);

if (keyExperimentVariables != null) {
for (final ExperimentVariables experimentVariables : keyExperimentVariables) {
for (final ContextExperiment experimentVariables : keyExperimentVariables) {
final Assignment assignment = getAssignment(experimentVariables.data.name);
if (assignment.assigned || assignment.overridden) {
return assignment;
Expand All @@ -807,7 +858,7 @@ private Assignment getVariableAssignment(final String key) {
return null;
}

private ExperimentVariables getExperiment(final String experimentName) {
private ContextExperiment getExperiment(final String experimentName) {
try {
dataLock_.readLock().lock();
return index_.get(experimentName);
Expand All @@ -816,7 +867,7 @@ private ExperimentVariables getExperiment(final String experimentName) {
}
}

private List<ExperimentVariables> getVariableExperiments(final String key) {
private List<ContextExperiment> getVariableExperiments(final String key) {
return Concurrency.getRW(dataLock_, indexVariables_, key);
}

Expand Down Expand Up @@ -891,52 +942,79 @@ private void clearRefreshTimer() {
}
}

private static class ExperimentVariables {
private static class ContextExperiment {
Experiment data;
ArrayList<Map<String, Object>> variables;
List<Map<String, Object>> variables;
Map<String, ContextCustomFieldValue> customFieldValues;
}

private static class ContextCustomFieldValue {
String type;
Object value;
}

private void setData(final ContextData data) {
final Map<String, ExperimentVariables> index = new HashMap<String, ExperimentVariables>();
final Map<String, List<ExperimentVariables>> indexVariables = new HashMap<String, List<ExperimentVariables>>();
final Map<String, ContextExperiment> index = new HashMap<String, ContextExperiment>();
final Map<String, List<ContextExperiment>> indexVariables = new HashMap<String, List<ContextExperiment>>();

for (final Experiment experiment : data.experiments) {
final ExperimentVariables experimentVariables = new ExperimentVariables();
experimentVariables.data = experiment;
experimentVariables.variables = new ArrayList<Map<String, Object>>(experiment.variants.length);
final ContextExperiment contextExperiment = new ContextExperiment();
contextExperiment.data = experiment;
contextExperiment.variables = new ArrayList<Map<String, Object>>(experiment.variants.length);

for (final ExperimentVariant variant : experiment.variants) {
if ((variant.config != null) && !variant.config.isEmpty()) {
final Map<String, Object> variables = variableParser_.parse(this, experiment.name, variant.name,
variant.config);
for (final String key : variables.keySet()) {
List<ExperimentVariables> keyExperimentVariables = indexVariables.get(key);
List<ContextExperiment> keyExperimentVariables = indexVariables.get(key);
if (keyExperimentVariables == null) {
keyExperimentVariables = new ArrayList<ExperimentVariables>();
keyExperimentVariables = new ArrayList<ContextExperiment>();
indexVariables.put(key, keyExperimentVariables);
}

int at = Collections.binarySearch(keyExperimentVariables, experimentVariables,
new Comparator<ExperimentVariables>() {
int at = Collections.binarySearch(keyExperimentVariables, contextExperiment,
new Comparator<ContextExperiment>() {
@Override
public int compare(ExperimentVariables a, ExperimentVariables b) {
public int compare(ContextExperiment a, ContextExperiment b) {
return Integer.valueOf(a.data.id).compareTo(b.data.id);
}
});

if (at < 0) {
at = -at - 1;
keyExperimentVariables.add(at, experimentVariables);
keyExperimentVariables.add(at, contextExperiment);
}
}

experimentVariables.variables.add(variables);
contextExperiment.variables.add(variables);
} else {
experimentVariables.variables.add(Collections.<String, Object> emptyMap());
contextExperiment.variables.add(Collections.<String, Object> emptyMap());
}
}

contextExperiment.customFieldValues = new HashMap<String, ContextCustomFieldValue>();
if (experiment.customFieldValues != null) {
for (final CustomFieldValue customFieldValue : experiment.customFieldValues) {
final ContextCustomFieldValue value = new ContextCustomFieldValue();
contextExperiment.customFieldValues.put(customFieldValue.name, value);

value.type = customFieldValue.type;
if (customFieldValue.value != null) {
if (customFieldValue.type.startsWith("json")) {
value.value = variableParser_.parse(this, experiment.name, customFieldValue.value);
} else if (customFieldValue.type.equals("boolean")) {
value.value = Boolean.parseBoolean(customFieldValue.value);
} else if (customFieldValue.type.equals("number")) {
value.value = Double.parseDouble(customFieldValue.value);
} else {
value.value = customFieldValue.value;
}
}
}
}

index.put(experiment.name, experimentVariables);
index.put(experiment.name, contextExperiment);
}

try {
Expand All @@ -955,8 +1033,8 @@ public int compare(ExperimentVariables a, ExperimentVariables b) {
private void setDataFailed(final Throwable exception) {
try {
dataLock_.writeLock().lock();
index_ = new HashMap<String, ExperimentVariables>();
indexVariables_ = new HashMap<String, List<ExperimentVariables>>();
index_ = new HashMap<String, ContextExperiment>();
indexVariables_ = new HashMap<String, List<ContextExperiment>>();
data_ = new ContextData();
failed_ = true;
} finally {
Expand Down Expand Up @@ -993,9 +1071,8 @@ private void logError(Throwable error) {

private final ReentrantReadWriteLock dataLock_ = new ReentrantReadWriteLock();
private ContextData data_;
private Map<String, ExperimentVariables> index_;
private Map<String, List<ExperimentVariables>> indexVariables_;

private Map<String, ContextExperiment> index_;
private Map<String, List<ContextExperiment>> indexVariables_;
private final ReentrantReadWriteLock contextLock_ = new ReentrantReadWriteLock();

private final Map<String, byte[]> hashedUnits_;
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -22,17 +22,29 @@ public DefaultVariableParser() {
objectMapper.enable(MapperFeature.USE_STATIC_TYPING);
this.reader_ = objectMapper
.readerFor(TypeFactory.defaultInstance().constructMapType(HashMap.class, String.class, Object.class));
this.readerGeneric_ = objectMapper.readerFor(Object.class);
}

public Map<String, Object> parse(@Nonnull final Context context, @Nonnull final String experimentName,
@Nonnull final String variantName, @Nonnull final String config) {
@Nonnull final String variantName, @Nonnull final String variableValues) {
try {
return reader_.readValue(config);
return reader_.readValue(variableValues);
} catch (IOException e) {
log.error("", e);
return null;
}
}

public Object parse(@Nonnull final Context context, @Nonnull final String experimentName,
@Nonnull final String variableValue) {
try {
return readerGeneric_.readValue(variableValue);
} catch (IOException e) {
log.error("", e);
return null;
}
}

private final ObjectReader reader_;
private final ObjectReader readerGeneric_;
}
4 changes: 3 additions & 1 deletion core-api/src/main/java/com/absmartly/sdk/VariableParser.java
Original file line number Diff line number Diff line change
Expand Up @@ -7,5 +7,7 @@
public interface VariableParser {
Map<String, Object> parse(@Nonnull final Context context, @Nonnull final String experimentName,
@Nonnull final String variantName,
@Nonnull String variableValue);
@Nonnull String variableValues);

Object parse(@Nonnull final Context context, @Nonnull final String experimentName, @Nonnull String variableValue);
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,47 @@
package com.absmartly.sdk.json;

import com.fasterxml.jackson.annotation.JsonIgnoreProperties;
import com.fasterxml.jackson.annotation.JsonInclude;

@JsonInclude(JsonInclude.Include.NON_NULL)
@JsonIgnoreProperties(ignoreUnknown = true)
public class CustomFieldValue {
public String name;
public String type;
public String value;

public CustomFieldValue() {}

@Override
public boolean equals(Object o) {
if (this == o)
return true;
if (o == null || getClass() != o.getClass())
return false;

CustomFieldValue that = (CustomFieldValue) o;

if (name != null ? !name.equals(that.name) : that.name != null)
return false;
if (type != null ? !type.equals(that.type) : that.type != null)
return false;
return value != null ? value.equals(that.value) : that.value == null;
}

@Override
public int hashCode() {
int result = name != null ? name.hashCode() : 0;
result = 31 * result + (type != null ? type.hashCode() : 0);
result = 31 * result + (value != null ? value.hashCode() : 0);
return result;
}

@Override
public String toString() {
return "CustomFieldValue{" +
"name='" + name + '\'' +
", type='" + type + '\'' +
", value='" + value + '\'' +
'}';
}
}

0 comments on commit b7fe53c

Please sign in to comment.