This is an automated email from the ASF dual-hosted git repository. jsinovassinnaik pushed a commit to branch UNOMI-817 in repository https://gitbox.apache.org/repos/asf/unomi.git
commit 4aa78efc4b5fe686bda6849e512a56145a9ce097 Author: jsinovassin <jsinovassinn...@jahia.com> AuthorDate: Mon Apr 29 15:36:36 2024 +0200 feedback --- .../apache/unomi/api/utils/ConditionHelper.java | 113 +++++++++++++++++++++ .../resources/OSGI-INF/blueprint/blueprint.xml | 7 +- .../actions/SetEventOccurenceCountAction.java | 29 ++++-- .../PastEventConditionESQueryBuilder.java | 102 ++++++------------- .../conditions/PastEventConditionEvaluator.java | 17 ++-- .../painless/updatePastEventOccurences.painless | 31 ++++-- .../resources/OSGI-INF/blueprint/blueprint.xml | 2 +- .../services/impl/segments/SegmentServiceImpl.java | 80 ++++++--------- .../2.5.0/update_pastEvents_profile.painless | 1 - 9 files changed, 228 insertions(+), 154 deletions(-) diff --git a/api/src/main/java/org/apache/unomi/api/utils/ConditionHelper.java b/api/src/main/java/org/apache/unomi/api/utils/ConditionHelper.java new file mode 100644 index 000000000..1a170987d --- /dev/null +++ b/api/src/main/java/org/apache/unomi/api/utils/ConditionHelper.java @@ -0,0 +1,113 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one or more + * contributor license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright ownership. + * The ASF licenses this file to You 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 org.apache.unomi.api.utils; + +import org.apache.unomi.api.conditions.Condition; +import org.apache.unomi.api.services.DefinitionsService; + +import java.util.List; +import java.util.Set; + +/** + * Utility class for creating various types of {@link Condition} objects. + * This class provides methods to easily construct conditions used for querying data based on specific criteria. + */ +public class ConditionHelper { + + private DefinitionsService definitionsService; + + /** + * Constructs a new ConditionHelper with a specified DefinitionsService. + * + * @param definitionsService the DefinitionsService to use for obtaining condition types. + */ + public ConditionHelper(DefinitionsService definitionsService) { + this.definitionsService = definitionsService; + } + + public void setDefinitionsService(DefinitionsService definitionsService) { + this.definitionsService = definitionsService; + } + + + /** + * Creates a profile property condition. + * This condition is used to evaluate specific properties of profiles. + * + * @param propertyName the name of the property to check. + * @param operator the comparison operator to apply. + * @param value the value to compare against the profile property. + * @param valueKey the key of the parameter where to store the value. Can be one of + * [propertyValue, propertyValueInteger, propertyValueDouble, propertyValueDate, propertyValueDateExpr, propertyValues, propertyValuesInteger, propertyValuesDouble, propertyValuesDate, propertyValuesDateExpr] + * @return a new Condition to evaluate the specified profile property. + */ + public Condition createProfilePropertyCondition(String propertyName, String operator, Object value, String valueKey) { + Condition condition = new Condition(definitionsService.getConditionType("profilePropertyCondition")); + condition.setParameter("propertyName", propertyName); + condition.setParameter("comparisonOperator", operator); + condition.setParameter(valueKey, value); + return condition; + } + + /** + * Creates a boolean condition. + * This condition can combine multiple sub-conditions using a logical operator (e.g., AND, OR). + * + * @param operator the logical operator to use for combining sub-conditions. + * @param subConditions the list of sub-conditions to combine. + * @return a new Condition configured with the specified logical operator and sub-conditions. + */ + public Condition createBooleanCondition(String operator, List<Condition> subConditions) { + Condition condition = new Condition(definitionsService.getConditionType("booleanCondition")); + condition.setParameter("operator", operator); + condition.setParameter("subConditions", subConditions); + return condition; + } + + /** + * Creates a nested condition. + * This condition applies another condition to a nested path within a profile or other entity. + * + * @param path the path within the entity to which the condition should apply. + * @param subCondition the condition to apply at the specified path. + * @return a new Condition configured to evaluate the specified path with the provided sub-condition. + */ + public Condition createNestedCondition(String path, Condition subCondition) { + Condition nestedCondition = new Condition(definitionsService.getConditionType("nestedCondition")); + nestedCondition.setParameter("path", path); + nestedCondition.setParameter("subCondition", subCondition); + return nestedCondition; + } + + + /** + * Creates a condition to filter profiles based on a set of IDs. + * This method constructs a condition that specifies which profiles to include or exclude based on their IDs. + * + * @param ids a {@link Set} of String representing the profile IDs to match. + * @param shouldMatch a boolean indicating whether the condition should match the profiles with the specified IDs or not. + * @return a new Condition configured to filter profiles based on the specified IDs and matching criteria. + */ + public Condition createProfileIdsCondition(Set<String> ids, boolean shouldMatch) { + Condition idsCondition = new Condition(); + idsCondition.setConditionType(definitionsService.getConditionType("idsCondition")); + idsCondition.setParameter("ids", ids); + idsCondition.setParameter("match", shouldMatch); + return idsCondition; + } + +} diff --git a/persistence-elasticsearch/core/src/main/resources/OSGI-INF/blueprint/blueprint.xml b/persistence-elasticsearch/core/src/main/resources/OSGI-INF/blueprint/blueprint.xml index 32efdd022..d14235171 100644 --- a/persistence-elasticsearch/core/src/main/resources/OSGI-INF/blueprint/blueprint.xml +++ b/persistence-elasticsearch/core/src/main/resources/OSGI-INF/blueprint/blueprint.xml @@ -20,12 +20,7 @@ xmlns:cm="http://aries.apache.org/blueprint/xmlns/blueprint-cm/v1.1.0" xmlns="http://www.osgi.org/xmlns/blueprint/v1.0.0" xsi:schemaLocation="http://www.osgi.org/xmlns/blueprint/v1.0.0 http://www.osgi.org/xmlns/blueprint/v1.0.0/blueprint.xsd - - - - - - http://aries.apache.org/blueprint/xmlns/blueprint-cm/v1.1.0 http://aries.apache.org/schemas/blueprint-cm/blueprint-cm-1.1.0.xsd"> + http://aries.apache.org/blueprint/xmlns/blueprint-cm/v1.1.0 http://aries.apache.org/schemas/blueprint-cm/blueprint-cm-1.1.0.xsd"> <cm:property-placeholder persistent-id="org.apache.unomi.persistence.elasticsearch" update-strategy="reload" placeholder-prefix="${es."> diff --git a/plugins/baseplugin/src/main/java/org/apache/unomi/plugins/baseplugin/actions/SetEventOccurenceCountAction.java b/plugins/baseplugin/src/main/java/org/apache/unomi/plugins/baseplugin/actions/SetEventOccurenceCountAction.java index e70922d04..6986d5184 100644 --- a/plugins/baseplugin/src/main/java/org/apache/unomi/plugins/baseplugin/actions/SetEventOccurenceCountAction.java +++ b/plugins/baseplugin/src/main/java/org/apache/unomi/plugins/baseplugin/actions/SetEventOccurenceCountAction.java @@ -129,17 +129,28 @@ public class SetEventOccurenceCountAction implements ActionExecutor { } private boolean updatePastEvents(Event event, String generatedPropertyKey, long count) { - ArrayList<Map<String, Object>> pastEvents = new ArrayList<>(); - ArrayList<Map<String, Object>> existingPastEvents = (ArrayList<Map<String, Object>>) event.getProfile().getSystemProperties().get("pastEvents"); - if (existingPastEvents != null) { - pastEvents.addAll(existingPastEvents.stream().filter(pastEvent -> !pastEvent.get("key").equals(generatedPropertyKey)).collect(Collectors.toList())); + List<Map<String, Object>> existingPastEvents = (List<Map<String, Object>>) event.getProfile().getSystemProperties().get("pastEvents"); + if (existingPastEvents == null) { + existingPastEvents = new ArrayList<>(); + event.getProfile().getSystemProperties().put("pastEvents", existingPastEvents); } - Map<String, Object> pastEvent = new HashMap<>(); - pastEvent.put("key", generatedPropertyKey); - pastEvent.put("count", count); - pastEvents.add(pastEvent); - return PropertyHelper.setProperty(event.getProfile(), "systemProperties.pastEvents", pastEvents, "alwaysSet"); + for (Map<String, Object> pastEvent : existingPastEvents) { + if (generatedPropertyKey.equals(pastEvent.get("key"))) { + pastEvent.put("count", count); + return true; + } + } + + return addNewPastEvent(existingPastEvents, generatedPropertyKey, count); + } + + private boolean addNewPastEvent(List<Map<String, Object>> existingPastEvents, String key, long count) { + Map<String, Object> newPastEvent = new HashMap<>(); + newPastEvent.put("key", key); + newPastEvent.put("count", count); + existingPastEvents.add(newPastEvent); + return true; // New event added } private boolean inTimeRange(LocalDateTime eventTime, Integer numberOfDays, LocalDateTime fromDate, LocalDateTime toDate) { diff --git a/plugins/baseplugin/src/main/java/org/apache/unomi/plugins/baseplugin/conditions/PastEventConditionESQueryBuilder.java b/plugins/baseplugin/src/main/java/org/apache/unomi/plugins/baseplugin/conditions/PastEventConditionESQueryBuilder.java index 772be5f71..07a9af63d 100644 --- a/plugins/baseplugin/src/main/java/org/apache/unomi/plugins/baseplugin/conditions/PastEventConditionESQueryBuilder.java +++ b/plugins/baseplugin/src/main/java/org/apache/unomi/plugins/baseplugin/conditions/PastEventConditionESQueryBuilder.java @@ -23,6 +23,7 @@ import org.apache.unomi.api.conditions.Condition; import org.apache.unomi.api.conditions.ConditionType; import org.apache.unomi.api.services.DefinitionsService; import org.apache.unomi.api.services.SegmentService; +import org.apache.unomi.api.utils.ConditionHelper; import org.apache.unomi.persistence.elasticsearch.conditions.ConditionContextHelper; import org.apache.unomi.persistence.elasticsearch.conditions.ConditionESQueryBuilder; import org.apache.unomi.persistence.elasticsearch.conditions.ConditionESQueryBuilderDispatcher; @@ -45,6 +46,8 @@ public class PastEventConditionESQueryBuilder implements ConditionESQueryBuilder private int aggregateQueryBucketSize = 5000; private boolean pastEventsDisablePartitions = false; + private ConditionHelper conditionHelper; + public void setDefinitionsService(DefinitionsService definitionsService) { this.definitionsService = definitionsService; } @@ -73,6 +76,9 @@ public class PastEventConditionESQueryBuilder implements ConditionESQueryBuilder this.segmentService = segmentService; } + public void init(){ + conditionHelper = new ConditionHelper(definitionsService); + } @Override public QueryBuilder buildQuery(Condition condition, Map<String, Object> context, ConditionESQueryBuilderDispatcher dispatcher) { boolean eventsOccurred = getStrategyFromOperator((String) condition.getParameter("operator")); @@ -88,7 +94,7 @@ public class PastEventConditionESQueryBuilder implements ConditionESQueryBuilder // TODO see for deprecation, this should not happen anymore each past event condition should have a generatedPropertyKey Condition eventCondition = getEventCondition(condition, context, null, definitionsService, scriptExecutor); Set<String> ids = getProfileIdsMatchingEventCount(eventCondition, minimumEventCount, maximumEventCount); - return dispatcher.buildFilter(getProfileIdsCondition(ids, eventsOccurred), context); + return dispatcher.buildFilter(conditionHelper.createProfileIdsCondition(ids, eventsOccurred), context); } } @@ -111,7 +117,7 @@ public class PastEventConditionESQueryBuilder implements ConditionESQueryBuilder } Set<String> profileIds = getProfileIdsMatchingEventCount(eventCondition, minimumEventCount, maximumEventCount); - return eventsOccurred ? profileIds.size() : persistenceService.queryCount(getProfileIdsCondition(profileIds, false), Profile.ITEM_TYPE); + return eventsOccurred ? profileIds.size() : persistenceService.queryCount(conditionHelper.createProfileIdsCondition(profileIds, false), Profile.ITEM_TYPE); } } @@ -122,84 +128,40 @@ public class PastEventConditionESQueryBuilder implements ConditionESQueryBuilder return operator == null || operator.equals("eventsOccurred"); } - private Condition getProfileIdsCondition(Set<String> ids, boolean shouldMatch) { - Condition idsCondition = new Condition(); - idsCondition.setConditionType(definitionsService.getConditionType("idsCondition")); - idsCondition.setParameter("ids", ids); - idsCondition.setParameter("match", shouldMatch); - return idsCondition; - } - private Condition getProfileConditionForCounter(String generatedPropertyKey, Integer minimumEventCount, Integer maximumEventCount, boolean eventsOccurred) { - Condition countCondition = new Condition(); - - countCondition.setConditionType(definitionsService.getConditionType("nestedCondition")); - countCondition.setParameter("path", "systemProperties.pastEvents"); - - Condition subConditionCount = new Condition(definitionsService.getConditionType("profilePropertyCondition")); - - Condition subConditionKey = getKeyEqualsCondition(generatedPropertyKey); - - ConditionType profilePropertyConditionType = definitionsService.getConditionType("profilePropertyCondition"); if (eventsOccurred) { - subConditionCount.setParameter("propertyName", "systemProperties.pastEvents.count"); - subConditionCount.setParameter("comparisonOperator", "between"); - subConditionCount.setParameter("propertyValuesInteger", Arrays.asList(minimumEventCount, maximumEventCount)); - - Condition booleanCondition = new Condition(definitionsService.getConditionType("booleanCondition")); - booleanCondition.setParameter("operator", "and"); - booleanCondition.setParameter("subConditions", Arrays.asList(subConditionCount, subConditionKey)); - - countCondition.setParameter("subCondition", booleanCondition); - return countCondition; - + return createEventOccurredCondition(generatedPropertyKey, minimumEventCount, maximumEventCount); } else { + return createEventNotOccurredCondition(generatedPropertyKey); + } + } - // 1. Key not present in profile - Condition keyNestedCondition = new Condition(); - keyNestedCondition.setConditionType(definitionsService.getConditionType("nestedCondition")); - keyNestedCondition.setParameter("path", "systemProperties.pastEvents"); - - Condition keyEquals = new Condition(profilePropertyConditionType); - keyEquals.setParameter("propertyName", "systemProperties.pastEvents.key"); - keyEquals.setParameter("comparisonOperator", "equals"); - keyEquals.setParameter("propertyValue", generatedPropertyKey); - - keyNestedCondition.setParameter("subCondition", keyEquals); - - Condition mustNotExist = new Condition(definitionsService.getConditionType("notCondition")); - mustNotExist.setParameter("subCondition", keyNestedCondition); + private Condition createEventOccurredCondition(String generatedPropertyKey, Integer minimumEventCount, Integer maximumEventCount) { + Condition subConditionCount = conditionHelper.createProfilePropertyCondition("systemProperties.pastEvents.count", "between", Arrays.asList(minimumEventCount, maximumEventCount), "propertyValuesInteger"); + Condition subConditionKey = conditionHelper.createProfilePropertyCondition("systemProperties.pastEvents.key", "equals", generatedPropertyKey, "propertyValue"); - // 2. Key present in profile but value equals to 0 - Condition counterZero = new Condition(profilePropertyConditionType); - counterZero.setParameter("propertyName", "systemProperties.pastEvents.count"); - counterZero.setParameter("comparisonOperator", "equals"); - counterZero.setParameter("propertyValueInteger", 0); + Condition booleanCondition = conditionHelper.createBooleanCondition("and", Arrays.asList(subConditionCount, subConditionKey)); + return conditionHelper.createNestedCondition("systemProperties.pastEvents", booleanCondition); + } - Condition keyExistsAndCounterZero = new Condition(definitionsService.getConditionType("booleanCondition")); - keyExistsAndCounterZero.setParameter("operator", "and"); - keyExistsAndCounterZero.setParameter("subConditions", Arrays.asList(subConditionKey, counterZero)); + private Condition createEventNotOccurredCondition(String generatedPropertyKey) { + Condition pastEventsNotExist = createPastEventMustNotExistCondition(generatedPropertyKey); + Condition counterZero = conditionHelper.createProfilePropertyCondition("systemProperties.pastEvents.count", "equals", 0, "propertyValueInteger"); + Condition keyEquals = conditionHelper.createProfilePropertyCondition("systemProperties.pastEvents.key", "equals", generatedPropertyKey, "propertyValue"); + Condition keyExistsAndCounterZero = conditionHelper.createBooleanCondition("and", Arrays.asList(keyEquals, counterZero)); + Condition nestedKeyExistsAndCounterZero = conditionHelper.createNestedCondition("systemProperties.pastEvents", keyExistsAndCounterZero); - Condition nestedKeyExistsAndCounterZero = new Condition(); - nestedKeyExistsAndCounterZero.setConditionType(definitionsService.getConditionType("nestedCondition")); - nestedKeyExistsAndCounterZero.setParameter("path", "systemProperties.pastEvents"); - nestedKeyExistsAndCounterZero.setParameter("subCondition", keyExistsAndCounterZero); + return conditionHelper.createBooleanCondition("or", Arrays.asList(pastEventsNotExist, nestedKeyExistsAndCounterZero)); + } - Condition counterCondition = new Condition(); - counterCondition.setConditionType(definitionsService.getConditionType("booleanCondition")); - counterCondition.setParameter("operator", "or"); - counterCondition.setParameter("subConditions", Arrays.asList(mustNotExist, nestedKeyExistsAndCounterZero)); + private Condition createPastEventMustNotExistCondition(String generatedPropertyKey) { + Condition keyEquals = conditionHelper.createProfilePropertyCondition("systemProperties.pastEvents.key", "equals", generatedPropertyKey, "propertyValue"); - return counterCondition; - } - } + Condition keyNestedCondition = conditionHelper.createNestedCondition("systemProperties.pastEvents", keyEquals); - private Condition getKeyEqualsCondition(String generatedPropertyKey) { - Condition subConditionKey = new Condition(definitionsService.getConditionType("profilePropertyCondition")); - subConditionKey.setParameter("propertyName", "systemProperties.pastEvents.key"); - subConditionKey.setParameter("comparisonOperator", "equals"); - subConditionKey.setParameter("propertyValue", generatedPropertyKey); - return subConditionKey; + Condition mustNotExist = new Condition(definitionsService.getConditionType("notCondition")); + mustNotExist.setParameter("subCondition", keyNestedCondition); + return mustNotExist; } private Set<String> getProfileIdsMatchingEventCount(Condition eventCondition, int minimumEventCount, int maximumEventCount) { diff --git a/plugins/baseplugin/src/main/java/org/apache/unomi/plugins/baseplugin/conditions/PastEventConditionEvaluator.java b/plugins/baseplugin/src/main/java/org/apache/unomi/plugins/baseplugin/conditions/PastEventConditionEvaluator.java index 58d912b42..f14f768f6 100644 --- a/plugins/baseplugin/src/main/java/org/apache/unomi/plugins/baseplugin/conditions/PastEventConditionEvaluator.java +++ b/plugins/baseplugin/src/main/java/org/apache/unomi/plugins/baseplugin/conditions/PastEventConditionEvaluator.java @@ -57,15 +57,14 @@ public class PastEventConditionEvaluator implements ConditionEvaluator { if (parameters.containsKey("generatedPropertyKey")) { String key = (String) parameters.get("generatedPropertyKey"); Profile profile = (Profile) item; - Object rawPastEvents = profile.getSystemProperties().get("pastEvents"); - if (rawPastEvents != null) { - List<Map<String, Object>> pastEvents = (ArrayList<Map<String, Object>>) rawPastEvents; - Number l = (Number) pastEvents - .stream() - .filter(pastEvent -> pastEvent.get("key").equals(key)) - .findFirst() - .map(pastEvent -> pastEvent.get("count")).orElse(0L); - count = l.longValue(); + List<Map<String, Object>> pastEvents = (ArrayList<Map<String, Object>>) profile.getSystemProperties().get("pastEvents"); + if (pastEvents != null) { + Number l = (Number) pastEvents + .stream() + .filter(pastEvent -> pastEvent.get("key").equals(key)) + .findFirst() + .map(pastEvent -> pastEvent.get("count")).orElse(0L); + count = l.longValue(); } else { count = 0; } diff --git a/tools/shell-commands/src/main/resources/requestBody/2.5.0/update_pastEvents_profile.painless b/plugins/baseplugin/src/main/resources/META-INF/cxs/painless/updatePastEventOccurences.painless similarity index 53% copy from tools/shell-commands/src/main/resources/requestBody/2.5.0/update_pastEvents_profile.painless copy to plugins/baseplugin/src/main/resources/META-INF/cxs/painless/updatePastEventOccurences.painless index e6981f033..406fe1051 100644 --- a/tools/shell-commands/src/main/resources/requestBody/2.5.0/update_pastEvents_profile.painless +++ b/plugins/baseplugin/src/main/resources/META-INF/cxs/painless/updatePastEventOccurences.painless @@ -15,14 +15,27 @@ * limitations under the License. */ -if (ctx._source.systemProperties != null && ctx._source.systemProperties.pastEvents != null && ctx._source.systemProperties.pastEvents instanceof Map) { - Map updatedPastEvents = new HashMap(); - List listOfPastEvent = new ArrayList(); - for (pastEventKey in ctx._source.systemProperties.pastEvents.keySet()) { - Map pastEvent = new HashMap(); - pastEvent.put('key', pastEventKey); - pastEvent.put('count', ctx._source.systemProperties.pastEvents.get(pastEventKey)); - listOfPastEvent.add(pastEvent); +if (ctx._source.systemProperties == null) { + ctx._source.systemProperties = new HashMap(); +} + +if (ctx._source.systemProperties.pastEvents == null) { + ctx._source.systemProperties.pastEvents = new HashMap(); +} + +boolean exists = false; +for (pastEvent in ctx._source.systemProperties.pastEvents) { + if (pastEvent.get("key") == params.pastEventKey) { + pastEvent.put("count", params.valueToAdd); + exists = true } - ctx._source.systemProperties.pastEvents = listOfPastEvent; } + +if (!exists) { + Map newPastEvent = new HashMap(); + newPastEvent.put('key', params.pastEventKey); + newPastEvent.put('count', params.valueToAdd); + ctx._source.systemProperties.pastEvents.add(newPastEvent); +} + +ctx._source.systemProperties.put("lastUpdated", new Date()); diff --git a/plugins/baseplugin/src/main/resources/OSGI-INF/blueprint/blueprint.xml b/plugins/baseplugin/src/main/resources/OSGI-INF/blueprint/blueprint.xml index 9f7dc8c92..2b5f63fc4 100644 --- a/plugins/baseplugin/src/main/resources/OSGI-INF/blueprint/blueprint.xml +++ b/plugins/baseplugin/src/main/resources/OSGI-INF/blueprint/blueprint.xml @@ -113,7 +113,7 @@ <service-properties> <entry key="queryBuilderId" value="pastEventConditionESQueryBuilder"/> </service-properties> - <bean class="org.apache.unomi.plugins.baseplugin.conditions.PastEventConditionESQueryBuilder"> + <bean class="org.apache.unomi.plugins.baseplugin.conditions.PastEventConditionESQueryBuilder" init-method="init"> <property name="definitionsService" ref="definitionsService"/> <property name="persistenceService" ref="persistenceService"/> <property name="segmentService" ref="segmentService"/> diff --git a/services/src/main/java/org/apache/unomi/services/impl/segments/SegmentServiceImpl.java b/services/src/main/java/org/apache/unomi/services/impl/segments/SegmentServiceImpl.java index 04acfeee5..81f656bb4 100644 --- a/services/src/main/java/org/apache/unomi/services/impl/segments/SegmentServiceImpl.java +++ b/services/src/main/java/org/apache/unomi/services/impl/segments/SegmentServiceImpl.java @@ -20,11 +20,7 @@ package org.apache.unomi.services.impl.segments; import com.fasterxml.jackson.core.JsonProcessingException; import net.jodah.failsafe.Failsafe; import net.jodah.failsafe.RetryPolicy; -import org.apache.unomi.api.Event; -import org.apache.unomi.api.Item; -import org.apache.unomi.api.Metadata; -import org.apache.unomi.api.PartialList; -import org.apache.unomi.api.Profile; +import org.apache.unomi.api.*; import org.apache.unomi.api.actions.Action; import org.apache.unomi.api.conditions.Condition; import org.apache.unomi.api.conditions.ConditionType; @@ -35,6 +31,7 @@ import org.apache.unomi.api.services.EventService; import org.apache.unomi.api.services.RulesService; import org.apache.unomi.api.services.SchedulerService; import org.apache.unomi.api.services.SegmentService; +import org.apache.unomi.api.utils.ConditionHelper; import org.apache.unomi.persistence.spi.CustomObjectMapper; import org.apache.unomi.persistence.spi.aggregate.TermsAggregate; import org.apache.unomi.services.impl.AbstractServiceImpl; @@ -86,6 +83,9 @@ public class SegmentServiceImpl extends AbstractServiceImpl implements SegmentSe private boolean pastEventsDisablePartitions = false; private int dailyDateExprEvaluationHourUtc = 5; + private ConditionHelper conditionHelper; + + public SegmentServiceImpl() { logger.info("Initializing segment service..."); } @@ -158,6 +158,7 @@ public class SegmentServiceImpl extends AbstractServiceImpl implements SegmentSe } bundleContext.addBundleListener(this); initializeTimer(); + conditionHelper = new ConditionHelper(definitionsService); logger.info("Segment service initialized."); } @@ -877,40 +878,24 @@ public class SegmentServiceImpl extends AbstractServiceImpl implements SegmentSe * Return the list of profile ids, for profiles that already have an event count matching the generated property key * * @param generatedPropertyKey the generated property key of the generated rule for the given past event condition. - * @return the list of profile ids. + * @return the set of profile ids. */ private Set<String> getExistingProfilesWithPastEventOccurrenceCount(String generatedPropertyKey) { - Condition countExistsCondition = new Condition(); - - countExistsCondition.setConditionType(definitionsService.getConditionType("nestedCondition")); - countExistsCondition.setParameter("path", "systemProperties.pastEvents"); - - Condition subConditionCount = new Condition(definitionsService.getConditionType("profilePropertyCondition")); - subConditionCount.setParameter("propertyName", "systemProperties.pastEvents.count"); - subConditionCount.setParameter("comparisonOperator", "greaterThan"); - subConditionCount.setParameter("propertyValueInteger", 0); - - Condition subConditionKey = new Condition(definitionsService.getConditionType("profilePropertyCondition")); - subConditionKey.setParameter("propertyName", "systemProperties.pastEvents.key"); - subConditionKey.setParameter("comparisonOperator", "equals"); - subConditionKey.setParameter("propertyValue", generatedPropertyKey); - - Condition booleanCondition = new Condition(definitionsService.getConditionType("booleanCondition")); - booleanCondition.setParameter("operator", "and"); - booleanCondition.setParameter("subConditions", Arrays.asList(subConditionCount, subConditionKey)); - - countExistsCondition.setParameter("subCondition", booleanCondition); + Condition subConditionCount = conditionHelper.createProfilePropertyCondition("systemProperties.pastEvents.count", "greaterThan", 0, "propertyValueInteger"); + Condition subConditionKey = conditionHelper.createProfilePropertyCondition("systemProperties.pastEvents.key", "equals", generatedPropertyKey, "propertyValue"); + Condition booleanCondition = conditionHelper.createBooleanCondition("and", Arrays.asList(subConditionCount, subConditionKey)); + Condition condition = conditionHelper.createNestedCondition("systemProperties.pastEvents", booleanCondition); Set<String> profileIds = new HashSet<>(); if (pastEventsDisablePartitions) { - profileIds.addAll(persistenceService.aggregateWithOptimizedQuery(countExistsCondition, new TermsAggregate("itemId"), + profileIds.addAll(persistenceService.aggregateWithOptimizedQuery(condition, new TermsAggregate("itemId"), Profile.ITEM_TYPE, maximumIdsQueryCount).keySet()); } else { - Map<String, Double> m = persistenceService.getSingleValuesMetrics(countExistsCondition, new String[]{"card"}, "itemId.keyword", Profile.ITEM_TYPE); + Map<String, Double> m = persistenceService.getSingleValuesMetrics(condition, new String[]{"card"}, "itemId.keyword", Profile.ITEM_TYPE); long card = m.get("_card").longValue(); int numParts = (int) (card / aggregateQueryBucketSize) + 2; for (int i = 0; i < numParts; i++) { - profileIds.addAll(persistenceService.aggregateWithOptimizedQuery(countExistsCondition, new TermsAggregate("itemId", i, numParts), + profileIds.addAll(persistenceService.aggregateWithOptimizedQuery(condition, new TermsAggregate("itemId", i, numParts), Profile.ITEM_TYPE).keySet()); } } @@ -1001,38 +986,35 @@ public class SegmentServiceImpl extends AbstractServiceImpl implements SegmentSe * * @param eventCountByProfile the events count per profileId map * @param propertyKey the generate property key for this past event condition, to keep track of the count in the profile - * @return the list of profiles for witch the count of event occurrences have been updated. + * @return the set of profiles for witch the count of event occurrences have been updated. */ private Set<String> updatePastEventOccurrencesOnProfiles(Map<String, Long> eventCountByProfile, String propertyKey) { Set<String> profilesUpdated = new HashSet<>(); - Map<Item, Map> batch = new HashMap<>(); + Map<String, Map[]> batch = new HashMap<>(); Iterator<Map.Entry<String, Long>> entryIterator = eventCountByProfile.entrySet().iterator(); + while (entryIterator.hasNext()) { Map.Entry<String, Long> entry = entryIterator.next(); String profileId = entry.getKey(); if (!profileId.startsWith("_")) { - Profile storedProfile = persistenceService.load(profileId, Profile.class); - if (storedProfile != null) { - List<Map<String, Object>> pastEvents = new ArrayList<>(); - Map<String, Object> systemProperties = storedProfile.getSystemProperties() != null ? storedProfile.getSystemProperties() : new HashMap<>(); - if (systemProperties.containsKey("pastEvents")) { - pastEvents = (ArrayList<Map<String, Object>>) storedProfile.getSystemProperties().get("pastEvents"); - pastEvents.removeIf(map -> map.get("key").equals(propertyKey)); - } - pastEvents.add(Map.of("key", propertyKey, "count", entry.getValue())); - systemProperties.put("pastEvents", pastEvents); - systemProperties.put("lastUpdated", new Date()); - - Profile profile = new Profile(); - profile.setItemId(profileId); - batch.put(profile, Collections.singletonMap("systemProperties", systemProperties)); - profilesUpdated.add(profileId); - } + Map<String, Object> scriptParams = new HashMap<>(); + scriptParams.put("pastEventKey", propertyKey); + scriptParams.put("valueToAdd", entry.getValue()); + Map<String, Object>[] params = new Map[]{scriptParams}; + batch.put(profileId, params); + profilesUpdated.add(profileId); } + + + if (batch.size() == segmentUpdateBatchSize || (!entryIterator.hasNext() && !batch.isEmpty())) { try { - persistenceService.update(batch, Profile.class); + batch.forEach((id, params) -> { + Condition profileIdCondition = conditionHelper.createProfilePropertyCondition("itemId", "equals", id, "propertyValue"); + Condition[] conditions = new Condition[]{profileIdCondition}; + persistenceService.updateWithQueryAndStoredScript(Profile.class, new String[]{"updatePastEventOccurences"}, params, conditions); + }); } catch (Exception e) { logger.error("Error updating {} profiles for past event system properties", batch.size(), e); } finally { diff --git a/tools/shell-commands/src/main/resources/requestBody/2.5.0/update_pastEvents_profile.painless b/tools/shell-commands/src/main/resources/requestBody/2.5.0/update_pastEvents_profile.painless index e6981f033..9c6768084 100644 --- a/tools/shell-commands/src/main/resources/requestBody/2.5.0/update_pastEvents_profile.painless +++ b/tools/shell-commands/src/main/resources/requestBody/2.5.0/update_pastEvents_profile.painless @@ -16,7 +16,6 @@ */ if (ctx._source.systemProperties != null && ctx._source.systemProperties.pastEvents != null && ctx._source.systemProperties.pastEvents instanceof Map) { - Map updatedPastEvents = new HashMap(); List listOfPastEvent = new ArrayList(); for (pastEventKey in ctx._source.systemProperties.pastEvents.keySet()) { Map pastEvent = new HashMap();