This is an automated email from the ASF dual-hosted git repository. jkevan pushed a commit to branch fixMergeControlGroup1.x in repository https://gitbox.apache.org/repos/asf/unomi.git
commit 978929f69ef7bd69bcf236f554322ac5db34cba0 Author: Kevan <ke...@jahia.com> AuthorDate: Thu Nov 17 13:54:44 2022 +0100 UNOMI-691: fix personalization strategy status stored on profile during merge --- .../org/apache/unomi/itests/ProfileMergeIT.java | 84 +++++++++++++++++++++- .../services/impl/profiles/ProfileServiceImpl.java | 37 +++++++--- 2 files changed, 109 insertions(+), 12 deletions(-) diff --git a/itests/src/test/java/org/apache/unomi/itests/ProfileMergeIT.java b/itests/src/test/java/org/apache/unomi/itests/ProfileMergeIT.java index 4512d024e..fb1cc40de 100644 --- a/itests/src/test/java/org/apache/unomi/itests/ProfileMergeIT.java +++ b/itests/src/test/java/org/apache/unomi/itests/ProfileMergeIT.java @@ -24,6 +24,7 @@ import org.apache.unomi.api.conditions.Condition; import org.apache.unomi.api.rules.Rule; import org.apache.unomi.api.services.DefinitionsService; import org.apache.unomi.api.services.EventService; +import org.apache.unomi.api.services.ProfileService; import org.apache.unomi.api.services.RulesService; import org.junit.After; import org.junit.Assert; @@ -35,9 +36,7 @@ import org.ops4j.pax.exam.spi.reactors.PerSuite; import org.ops4j.pax.exam.util.Filter; import javax.inject.Inject; -import java.util.Collections; -import java.util.Date; -import java.util.HashMap; +import java.util.*; /** * Integration test for MergeProfilesOnPropertyAction @@ -45,6 +44,10 @@ import java.util.HashMap; @RunWith(PaxExam.class) @ExamReactorStrategy(PerSuite.class) public class ProfileMergeIT extends BaseIT { + public static final String PERSONALIZATION_STRATEGY_STATUS = "personalizationStrategyStatus"; + public static final String PERSONALIZATION_STRATEGY_STATUS_ID = "personalizationId"; + public static final String PERSONALIZATION_STRATEGY_STATUS_IN_CTRL_GROUP = "inControlGroup"; + public static final String PERSONALIZATION_STRATEGY_STATUS_DATE = "timeStamp"; @Inject @Filter(timeout = 600000) protected EventService eventService; @@ -52,6 +55,8 @@ public class ProfileMergeIT extends BaseIT { protected RulesService rulesService; @Inject @Filter(timeout = 600000) protected DefinitionsService definitionsService; + @Inject @Filter(timeout = 600000) + protected ProfileService profileService; private final static String TEST_EVENT_TYPE = "mergeProfileTestEventType"; private final static String TEST_RULE_ID = "mergeOnPropertyTest"; @@ -79,6 +84,79 @@ public class ProfileMergeIT extends BaseIT { Assert.assertEquals(sendEvent().getProfile().getItemId(), TEST_PROFILE_ID); } + @Test + public void testPersonalizationStrategyStatusMerge() { + // create some statuses for the tests: + Map<String, Object> perso1true = buildPersonalizationStrategyStatus("perso-test-1", true); + Map<String, Object> perso1false = buildPersonalizationStrategyStatus("perso-test-1", false); + Map<String, Object> perso2false = buildPersonalizationStrategyStatus("perso-test-2", false); + Map<String, Object> perso3true = buildPersonalizationStrategyStatus("perso-test-3", true); + + // create a single master profile we will keep along all the tests: + Profile profileMaster = new Profile("master-profile"); + + // merge test 1: Master do not have statuses, but slave have statuses + // master have: NULL + // slave have: perso-test-1 -> true + // perso-test-2 -> false + // Expected: perso-test-1 -> true + // perso-test-2 -> false + Profile profileSlave = new Profile("slave-profile"); + profileSlave.setSystemProperty(PERSONALIZATION_STRATEGY_STATUS, new ArrayList<>(Arrays.asList(perso1true, perso2false))); + profileMaster = profileService.mergeProfiles(profileMaster, Collections.singletonList(profileSlave)); + assertPersonalizationStrategyStatus(profileMaster, Arrays.asList(perso1true, perso2false)); + + // merge test 2: Master do not have statuses, but slave have statuses + // master have: perso-test-1 -> true + // perso-test-2 -> false + // slave have: NULL + // Expected: perso-test-1 -> true + // perso-test-2 -> false + profileSlave = new Profile("slave-profile"); + profileMaster = profileService.mergeProfiles(profileMaster, Collections.singletonList(profileSlave)); + assertPersonalizationStrategyStatus(profileMaster, Arrays.asList(perso1true, perso2false)); + + // merge test 3: both master and slave have strategy statuses and some are conflicting + // master have: perso-test-1 -> true + // perso-test-2 -> false + // slave have: perso-test-1 -> false (conflicting) + // perso-test-3 -> true + // Expected: perso-test-1 -> true + // perso-test-2 -> false + // perso-test-3 -> true + profileSlave = new Profile("slave-profile"); + profileSlave.setSystemProperty(PERSONALIZATION_STRATEGY_STATUS, new ArrayList<>(Arrays.asList(perso1false, perso3true))); + profileMaster = profileService.mergeProfiles(profileMaster, Collections.singletonList(profileSlave)); + assertPersonalizationStrategyStatus(profileMaster, Arrays.asList(perso1true, perso2false, perso3true)); + } + + private Map<String, Object> buildPersonalizationStrategyStatus(String persoId, boolean inControlGroup) { + Map<String, Object> personalizationStrategyStatus = new HashMap<>(); + personalizationStrategyStatus.put(PERSONALIZATION_STRATEGY_STATUS_ID, persoId); + personalizationStrategyStatus.put(PERSONALIZATION_STRATEGY_STATUS_DATE, new Date()); + personalizationStrategyStatus.put(PERSONALIZATION_STRATEGY_STATUS_IN_CTRL_GROUP, inControlGroup); + return personalizationStrategyStatus; + } + + private void assertPersonalizationStrategyStatus(Profile profile, List<Map<String, Object>> expectedStatuses) { + List<Map<String, Object>> strategyStatuses = (List<Map<String, Object>>) profile.getSystemProperties().get(PERSONALIZATION_STRATEGY_STATUS); + Assert.assertEquals("We didn't get the good number of expected personalization statuses on the given profile: " + profile.getItemId(), + expectedStatuses.size(), strategyStatuses.size()); + for (Map<String, Object> expectedStatus : expectedStatuses) { + Optional<Map<String, Object>> foundStatusOp = strategyStatuses + .stream() + .filter(strategyStatus -> strategyStatus.get(PERSONALIZATION_STRATEGY_STATUS_ID) + .equals(expectedStatus.get(PERSONALIZATION_STRATEGY_STATUS_ID))) + .findFirst(); + if (foundStatusOp.isPresent()) { + Map<String, Object> foundStatus = foundStatusOp.get(); + Assert.assertEquals(expectedStatus.get(PERSONALIZATION_STRATEGY_STATUS_DATE), foundStatus.get(PERSONALIZATION_STRATEGY_STATUS_DATE)); + Assert.assertEquals(expectedStatus.get(PERSONALIZATION_STRATEGY_STATUS_IN_CTRL_GROUP), foundStatus.get(PERSONALIZATION_STRATEGY_STATUS_IN_CTRL_GROUP)); + } else { + Assert.fail("We didn't found the expected personalization: " + expectedStatus.get(PERSONALIZATION_STRATEGY_STATUS_ID) + " status on the given profile: " + profile.getItemId()); + } + } + } private Event sendEvent() { Profile profile = new Profile(); profile.setProperties(new HashMap<>()); diff --git a/services/src/main/java/org/apache/unomi/services/impl/profiles/ProfileServiceImpl.java b/services/src/main/java/org/apache/unomi/services/impl/profiles/ProfileServiceImpl.java index f9a9a6a41..76c175955 100644 --- a/services/src/main/java/org/apache/unomi/services/impl/profiles/ProfileServiceImpl.java +++ b/services/src/main/java/org/apache/unomi/services/impl/profiles/ProfileServiceImpl.java @@ -33,6 +33,7 @@ import org.apache.unomi.persistence.spi.CustomObjectMapper; import org.apache.unomi.persistence.spi.PersistenceService; import org.apache.unomi.persistence.spi.PropertyHelper; import org.apache.unomi.services.impl.ParserHelper; +import org.apache.unomi.services.sorts.ControlGroupPersonalizationStrategy; import org.osgi.framework.*; import org.slf4j.Logger; import org.slf4j.LoggerFactory; @@ -1109,16 +1110,34 @@ public class ProfileServiceImpl implements ProfileService, SynchronousBundleList } } else if (targetProperty instanceof Collection && sourceProperty.getValue() instanceof Collection) { // merge Collections like "lists" - Collection sourceCollection = (Collection) sourceProperty.getValue(); - Collection targetCollection = (Collection) targetProperty; - - for (Object sourceItem : sourceCollection) { - if (!targetCollection.contains(sourceItem)) { - try { - targetCollection.add(sourceItem); + if (ControlGroupPersonalizationStrategy.PERSONALIZATION_STRATEGY_STATUS.equals(sourceProperty.getKey())) { + // Special handling for personalization strategy statuses + // TODO UNOMI-719: move this in a dedicated extension point to handle this kind of merge strategy in a more generic way + List<Map<String, Object>> sourceStatuses = (List<Map<String, Object>>) sourceProperty.getValue(); + List<Map<String, Object>> targetStatuses = (List<Map<String, Object>>) targetProperty; + + for (Map<String, Object> sourceStatus : sourceStatuses) { + if (targetStatuses + .stream() + .noneMatch(targetStatus -> targetStatus.get(ControlGroupPersonalizationStrategy.PERSONALIZATION_STRATEGY_STATUS_ID) + .equals(sourceStatus.get(ControlGroupPersonalizationStrategy.PERSONALIZATION_STRATEGY_STATUS_ID)))) { + // there is no existing status for the status ID, we can safely add it to master + targetStatuses.add(sourceStatus); changed = true; - } catch (Exception e) { - // may be Collection type issue + } + } + } else { + Collection sourceCollection = (Collection) sourceProperty.getValue(); + Collection targetCollection = (Collection) targetProperty; + + for (Object sourceItem : sourceCollection) { + if (!targetCollection.contains(sourceItem)) { + try { + targetCollection.add(sourceItem); + changed = true; + } catch (Exception e) { + // may be Collection type issue + } } } }