diff --git a/core-api/src/main/java/com/optimizely/ab/config/DatafileProjectConfig.java b/core-api/src/main/java/com/optimizely/ab/config/DatafileProjectConfig.java index 65edf5768..94341c717 100644 --- a/core-api/src/main/java/com/optimizely/ab/config/DatafileProjectConfig.java +++ b/core-api/src/main/java/com/optimizely/ab/config/DatafileProjectConfig.java @@ -74,6 +74,7 @@ public class DatafileProjectConfig implements ProjectConfig { private final List groups; private final List rollouts; private final List integrations; + private final Set allSegments; // key to entity mappings private final Map attributeKeyMapping; @@ -204,6 +205,15 @@ public DatafileProjectConfig(String accountId, this.publicKeyForODP = publicKeyForODP; this.hostForODP = hostForODP; + Set allSegments = new HashSet<>(); + if (typedAudiences != null) { + for(Audience audience: typedAudiences) { + allSegments.addAll(audience.getSegments()); + } + } + + this.allSegments = allSegments; + Map variationIdToExperimentMap = new HashMap(); for (Experiment experiment : this.experiments) { for (Variation variation : experiment.getVariations()) { @@ -424,6 +434,10 @@ public List getExperiments() { return experiments; } + public Set getAllSegments() { + return this.allSegments; + } + @Override public List getExperimentsForEventKey(String eventKey) { EventType event = eventNameMapping.get(eventKey); diff --git a/core-api/src/main/java/com/optimizely/ab/config/audience/AndCondition.java b/core-api/src/main/java/com/optimizely/ab/config/audience/AndCondition.java index 8d855e3e9..7865eb2d2 100644 --- a/core-api/src/main/java/com/optimizely/ab/config/audience/AndCondition.java +++ b/core-api/src/main/java/com/optimizely/ab/config/audience/AndCondition.java @@ -38,6 +38,7 @@ public AndCondition(@Nonnull List conditions) { this.conditions = conditions; } + @Override public List getConditions() { return conditions; } diff --git a/core-api/src/main/java/com/optimizely/ab/config/audience/Audience.java b/core-api/src/main/java/com/optimizely/ab/config/audience/Audience.java index 8db26844a..bfc1be85d 100644 --- a/core-api/src/main/java/com/optimizely/ab/config/audience/Audience.java +++ b/core-api/src/main/java/com/optimizely/ab/config/audience/Audience.java @@ -1,6 +1,6 @@ /** * - * Copyright 2016-2017, 2019, Optimizely and contributors + * Copyright 2016-2017, 2019, 2022, Optimizely and contributors * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. @@ -22,6 +22,9 @@ import com.optimizely.ab.config.IdKeyMapped; import javax.annotation.concurrent.Immutable; +import java.util.HashSet; +import java.util.List; +import java.util.Set; /** * Represents the Optimizely Audience configuration. @@ -69,4 +72,27 @@ public String toString() { ", conditions=" + conditions + '}'; } + + public Set getSegments() { + return getSegments(conditions); + } + + private static Set getSegments(Condition conditions) { + List nestedConditions = conditions.getConditions(); + Set segments = new HashSet<>(); + if (nestedConditions != null) { + for (Condition nestedCondition : nestedConditions) { + Set nestedSegments = getSegments(nestedCondition); + segments.addAll(nestedSegments); + } + } else { + if (conditions.getClass() == UserAttribute.class) { + UserAttribute userAttributeCondition = (UserAttribute) conditions; + if (UserAttribute.QUALIFIED.equals(userAttributeCondition.getMatch())) { + segments.add((String)userAttributeCondition.getValue()); + } + } + } + return segments; + } } diff --git a/core-api/src/main/java/com/optimizely/ab/config/audience/AudienceIdCondition.java b/core-api/src/main/java/com/optimizely/ab/config/audience/AudienceIdCondition.java index 4b3341d4c..9fc248522 100644 --- a/core-api/src/main/java/com/optimizely/ab/config/audience/AudienceIdCondition.java +++ b/core-api/src/main/java/com/optimizely/ab/config/audience/AudienceIdCondition.java @@ -27,6 +27,7 @@ import javax.annotation.Nullable; import javax.annotation.concurrent.Immutable; +import java.util.List; import java.util.Map; import java.util.Objects; @@ -97,6 +98,11 @@ public boolean equals(Object o) { (audienceId.equals(condition.audienceId))); } + @Override + public List getConditions() { + return null; + } + @Override public int hashCode() { diff --git a/core-api/src/main/java/com/optimizely/ab/config/audience/Condition.java b/core-api/src/main/java/com/optimizely/ab/config/audience/Condition.java index 105c9b8e0..ab3fe99af 100644 --- a/core-api/src/main/java/com/optimizely/ab/config/audience/Condition.java +++ b/core-api/src/main/java/com/optimizely/ab/config/audience/Condition.java @@ -20,6 +20,7 @@ import com.optimizely.ab.config.ProjectConfig; import javax.annotation.Nullable; +import java.util.List; import java.util.Map; /** @@ -33,4 +34,6 @@ public interface Condition { String toJson(); String getOperandOrId(); + + List getConditions(); } diff --git a/core-api/src/main/java/com/optimizely/ab/config/audience/EmptyCondition.java b/core-api/src/main/java/com/optimizely/ab/config/audience/EmptyCondition.java index 61e010317..1f7a87b12 100644 --- a/core-api/src/main/java/com/optimizely/ab/config/audience/EmptyCondition.java +++ b/core-api/src/main/java/com/optimizely/ab/config/audience/EmptyCondition.java @@ -21,7 +21,7 @@ import javax.annotation.Nullable; import java.util.Map; -public class EmptyCondition implements Condition { +public class EmptyCondition extends LeafCondition { @Nullable @Override public Boolean evaluate(ProjectConfig config, OptimizelyUserContext user) { diff --git a/core-api/src/main/java/com/optimizely/ab/config/audience/LeafCondition.java b/core-api/src/main/java/com/optimizely/ab/config/audience/LeafCondition.java new file mode 100644 index 000000000..a61c1650e --- /dev/null +++ b/core-api/src/main/java/com/optimizely/ab/config/audience/LeafCondition.java @@ -0,0 +1,26 @@ +/** + * Copyright 2022, Optimizely Inc. and contributors + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package com.optimizely.ab.config.audience; + +import java.util.List; + +public abstract class LeafCondition implements Condition { + + @Override + public List getConditions() { + return null; + } +} diff --git a/core-api/src/main/java/com/optimizely/ab/config/audience/NotCondition.java b/core-api/src/main/java/com/optimizely/ab/config/audience/NotCondition.java index 9e4b2fb86..45dec6637 100644 --- a/core-api/src/main/java/com/optimizely/ab/config/audience/NotCondition.java +++ b/core-api/src/main/java/com/optimizely/ab/config/audience/NotCondition.java @@ -22,6 +22,8 @@ import javax.annotation.Nullable; import javax.annotation.concurrent.Immutable; import javax.annotation.Nonnull; +import java.util.Arrays; +import java.util.List; /** @@ -41,6 +43,11 @@ public Condition getCondition() { return condition; } + @Override + public List getConditions() { + return Arrays.asList(condition); + } + @Nullable public Boolean evaluate(ProjectConfig config, OptimizelyUserContext user) { Boolean conditionEval = condition == null ? null : condition.evaluate(config, user); diff --git a/core-api/src/main/java/com/optimizely/ab/config/audience/NullCondition.java b/core-api/src/main/java/com/optimizely/ab/config/audience/NullCondition.java index 44ddee7ca..1e12b836e 100644 --- a/core-api/src/main/java/com/optimizely/ab/config/audience/NullCondition.java +++ b/core-api/src/main/java/com/optimizely/ab/config/audience/NullCondition.java @@ -21,7 +21,7 @@ import javax.annotation.Nullable; import java.util.Map; -public class NullCondition implements Condition { +public class NullCondition extends LeafCondition { @Nullable @Override public Boolean evaluate(ProjectConfig config, OptimizelyUserContext user) { diff --git a/core-api/src/main/java/com/optimizely/ab/config/audience/OrCondition.java b/core-api/src/main/java/com/optimizely/ab/config/audience/OrCondition.java index 12fbbb3b2..c0f3603eb 100644 --- a/core-api/src/main/java/com/optimizely/ab/config/audience/OrCondition.java +++ b/core-api/src/main/java/com/optimizely/ab/config/audience/OrCondition.java @@ -38,6 +38,7 @@ public OrCondition(@Nonnull List conditions) { this.conditions = conditions; } + @Override public List getConditions() { return conditions; } diff --git a/core-api/src/main/java/com/optimizely/ab/config/audience/UserAttribute.java b/core-api/src/main/java/com/optimizely/ab/config/audience/UserAttribute.java index e7ff16f31..c38b6c2a4 100644 --- a/core-api/src/main/java/com/optimizely/ab/config/audience/UserAttribute.java +++ b/core-api/src/main/java/com/optimizely/ab/config/audience/UserAttribute.java @@ -38,7 +38,7 @@ */ @Immutable @JsonIgnoreProperties(ignoreUnknown = true) -public class UserAttribute implements Condition { +public class UserAttribute extends LeafCondition { public static final String QUALIFIED = "qualified"; private static final Logger logger = LoggerFactory.getLogger(UserAttribute.class); diff --git a/core-api/src/test/java/com/optimizely/ab/config/audience/AudienceConditionEvaluationTest.java b/core-api/src/test/java/com/optimizely/ab/config/audience/AudienceConditionEvaluationTest.java index 5a69b35ee..5a88e8ad9 100644 --- a/core-api/src/test/java/com/optimizely/ab/config/audience/AudienceConditionEvaluationTest.java +++ b/core-api/src/test/java/com/optimizely/ab/config/audience/AudienceConditionEvaluationTest.java @@ -1840,4 +1840,40 @@ public void nullValueEvaluate() { assertNull(nullValueAttribute.evaluate(null, OTUtils.user(Collections.singletonMap(attributeName, attributeValue)))); assertNull(nullValueAttribute.evaluate(null, OTUtils.user((Collections.singletonMap(attributeName, ""))))); } + + @Test + public void getAllSegmentsFromAudience() { + Condition condition = createMultipleConditionAudienceAndOrODP(); + Audience audience = new Audience("1", "testAudience", condition); + assertEquals(new HashSet<>(Arrays.asList("odp-segment-1", "odp-segment-2", "odp-segment-3", "odp-segment-4")), audience.getSegments()); + + // ["and", "1", "2"] + List audience1And2 = new ArrayList<>(); + audience1And2.add(new UserAttribute("odp.audiences", "third_party_dimension", "qualified", "odp-segment-1")); + audience1And2.add(new UserAttribute("odp.audiences", "third_party_dimension", "qualified", "odp-segment-2")); + AndCondition audienceCondition1 = new AndCondition(audience1And2); + + // ["and", "3", "4"] + List audience3And4 = new ArrayList<>(); + audience3And4.add(new UserAttribute("odp.audiences", "third_party_dimension", "qualified", "odp-segment-3")); + audience3And4.add(new UserAttribute("odp.audiences", "third_party_dimension", "qualified", "odp-segment-4")); + AndCondition audienceCondition2 = new AndCondition(audience3And4); + + // ["or", "5", "6"] + List audience5And6 = new ArrayList<>(); + audience5And6.add(new UserAttribute("odp.audiences", "third_party_dimension", "qualified", "odp-segment-5")); + audience5And6.add(new UserAttribute("odp.audiences", "third_party_dimension", "qualified", "odp-segment-6")); + OrCondition audienceCondition3 = new OrCondition(audience5And6); + + + //['or', '1', '2', '3'] + List conditions = new ArrayList<>(); + conditions.add(audienceCondition1); + conditions.add(audienceCondition2); + conditions.add(audienceCondition3); + + OrCondition implicitOr = new OrCondition(conditions); + audience = new Audience("1", "testAudience", implicitOr); + assertEquals(new HashSet<>(Arrays.asList("odp-segment-1", "odp-segment-2", "odp-segment-3", "odp-segment-4", "odp-segment-5", "odp-segment-6")), audience.getSegments()); + } }