You are viewing a plain text version of this content. The canonical link for it is here.
Posted to dev@unomi.apache.org by sh...@apache.org on 2020/11/19 08:22:45 UTC
[unomi] branch master updated: UNOMI-366: Implement increment
interest event type & action (#201)
This is an automated email from the ASF dual-hosted git repository.
shuber pushed a commit to branch master
in repository https://gitbox.apache.org/repos/asf/unomi.git
The following commit(s) were added to refs/heads/master by this push:
new a535d03 UNOMI-366: Implement increment interest event type & action (#201)
a535d03 is described below
commit a535d034abe0e1f09933dfc423486690dad94a33
Author: anatol-sialitski <53...@users.noreply.github.com>
AuthorDate: Thu Nov 19 11:17:38 2020 +0300
UNOMI-366: Implement increment interest event type & action (#201)
* UNOMI-366: Implement increment interest event type & action
* UNOMI-366: Implement increment interest event type & action
---
.../apache/unomi/itests/IncrementInterestsIT.java | 140 +++++++++++++++++++++
.../main/resources/etc/custom.system.properties | 7 ++
.../actions/IncrementInterestAction.java | 118 +++++++++++++++++
.../META-INF/cxs/actions/incrementInterest.json | 19 +++
.../META-INF/cxs/conditions/incrementInterest.json | 22 ++++
.../META-INF/cxs/rules/incrementInterest.json | 21 ++++
.../resources/OSGI-INF/blueprint/blueprint.xml | 38 ++++--
7 files changed, 357 insertions(+), 8 deletions(-)
diff --git a/itests/src/test/java/org/apache/unomi/itests/IncrementInterestsIT.java b/itests/src/test/java/org/apache/unomi/itests/IncrementInterestsIT.java
new file mode 100644
index 0000000..9ebaa79
--- /dev/null
+++ b/itests/src/test/java/org/apache/unomi/itests/IncrementInterestsIT.java
@@ -0,0 +1,140 @@
+/*
+ * 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.itests;
+
+import java.util.Date;
+import java.util.HashMap;
+import java.util.Map;
+import java.util.Objects;
+import java.util.UUID;
+
+import javax.inject.Inject;
+
+import org.apache.unomi.api.Event;
+import org.apache.unomi.api.Profile;
+import org.apache.unomi.api.Topic;
+import org.apache.unomi.api.services.EventService;
+import org.apache.unomi.api.services.ProfileService;
+import org.apache.unomi.api.services.TopicService;
+import org.junit.Assert;
+import org.junit.Test;
+import org.junit.runner.RunWith;
+import org.ops4j.pax.exam.junit.PaxExam;
+import org.ops4j.pax.exam.spi.reactors.ExamReactorStrategy;
+import org.ops4j.pax.exam.spi.reactors.PerSuite;
+import org.ops4j.pax.exam.util.Filter;
+
+@RunWith(PaxExam.class)
+@ExamReactorStrategy(PerSuite.class)
+public class IncrementInterestsIT
+ extends BaseIT
+{
+
+ @Inject
+ @Filter(timeout = 600000)
+ protected ProfileService profileService;
+
+ @Inject
+ @Filter(timeout = 600000)
+ protected EventService eventService;
+
+ @Inject
+ @Filter(timeout = 600000)
+ protected TopicService topicService;
+
+ @Test
+ @SuppressWarnings("unchecked")
+ public void test()
+ throws InterruptedException
+ {
+ final Topic topic = createTopic( "topicId" );
+ final Profile profile = createProfile();
+
+ final Map<String, Double> interestsAsMap = new HashMap<>();
+ interestsAsMap.put( topic.getTopicId(), 50.0 );
+ interestsAsMap.put( "unknown", 10.0 );
+
+ final Event event = createEvent( profile, interestsAsMap );
+
+ try
+ {
+ int eventCode = eventService.send( event );
+
+ if ( eventCode == EventService.PROFILE_UPDATED )
+ {
+ Profile updatedProfile = profileService.save( event.getProfile() );
+
+ refreshPersistence();
+
+ Map<String, Double> interests = (Map<String, Double>) updatedProfile.getProperty( "interests" );
+
+ Assert.assertEquals( 0.5, interests.get( topic.getTopicId() ), 0.0 );
+ Assert.assertFalse( interests.containsKey( "unknown" ) );
+ }
+ }
+ finally
+ {
+ topicService.delete( topic.getItemId() );
+ profileService.delete( profile.getItemId(), false );
+ }
+ }
+
+ private Event createEvent( Profile profile, Map<String, Double> interestsAsMap )
+ {
+ final Event event = new Event( "incrementInterest", null, profile, null, null, profile, new Date() );
+
+ event.setPersistent( false );
+ event.setProperty( "interests", interestsAsMap );
+
+ return event;
+ }
+
+ private Topic createTopic( final String topicId )
+ throws InterruptedException
+ {
+ final Topic topic = new Topic();
+
+ topic.setTopicId( topicId );
+ topic.setItemId( topicId );
+ topic.setName( "topicName" );
+ topic.setScope( "scope" );
+
+ topicService.save( topic );
+
+ keepTrying( "Failed waiting for the creation of the topic for the IncrementInterestsIT test",
+ () -> topicService.load( topic.getItemId() ), Objects::nonNull, 1000, 100 );
+
+ return topic;
+ }
+
+ private Profile createProfile()
+ throws InterruptedException
+ {
+ final Profile profile = new Profile( UUID.randomUUID().toString() );
+
+ profile.setProperty( "firstName", "FirstName" );
+ profile.setProperty( "lastName", "LastName" );
+
+ profileService.save( profile );
+
+ keepTrying( "Failed waiting for the creation of the profile for the IncrementInterestsIT test",
+ () -> profileService.load( profile.getItemId() ), Objects::nonNull, 1000, 100 );
+
+ return profile;
+ }
+
+}
diff --git a/package/src/main/resources/etc/custom.system.properties b/package/src/main/resources/etc/custom.system.properties
index 4618ac0..2d681c2 100644
--- a/package/src/main/resources/etc/custom.system.properties
+++ b/package/src/main/resources/etc/custom.system.properties
@@ -371,3 +371,10 @@ org.apache.unomi.mailchimp.list.merge-fields.activate=${env:UNOMI_MAILCHIMP_LIST
org.apache.unomi.weatherUpdate.apiKey=${env:UNOMI_WEATHERUPDATE_APIKEY:-YOUR_WEATHER_APIKEY}
org.apache.unomi.weatherUpdate.url.base=${env:UNOMI_WEATHERUPDATE_URL_BASE:-http://api.openweathermap.org}
org.apache.unomi.weatherUpdate.url.attributes=${env:UNOMI_WEATHERUPDATE_URL_ATTRIBUTES:-data/2.5/weather}
+
+#######################################################################################################################
+## Settings for increment interests of profile ##
+#######################################################################################################################
+org.apache.unomi.interests.min_value=${env:UNOMI_INTERESTS_MIN_VALUE:-0.0}
+org.apache.unomi.interests.max_value=${env:UNOMI_INTERESTS_MAX_VALUE:-150.0}
+org.apache.unomi.interests.divider_value=${env:UNOMI_INTERESTS_DIVIDER_VALUE:-100.0}
diff --git a/plugins/baseplugin/src/main/java/org/apache/unomi/plugins/baseplugin/actions/IncrementInterestAction.java b/plugins/baseplugin/src/main/java/org/apache/unomi/plugins/baseplugin/actions/IncrementInterestAction.java
new file mode 100644
index 0000000..0d693b4
--- /dev/null
+++ b/plugins/baseplugin/src/main/java/org/apache/unomi/plugins/baseplugin/actions/IncrementInterestAction.java
@@ -0,0 +1,118 @@
+/*
+ * 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.plugins.baseplugin.actions;
+
+import java.util.Date;
+import java.util.HashMap;
+import java.util.Map;
+
+import org.apache.unomi.api.Event;
+import org.apache.unomi.api.Profile;
+import org.apache.unomi.api.actions.Action;
+import org.apache.unomi.api.actions.ActionExecutor;
+import org.apache.unomi.api.services.EventService;
+import org.apache.unomi.api.services.TopicService;
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
+
+public class IncrementInterestAction implements ActionExecutor {
+
+ private static final Logger LOG = LoggerFactory.getLogger(IncrementInterestAction.class.getName());
+
+ private static final String EVENT_INTERESTS_PROPERTY = "interests";
+
+ private static final String ACTION_INTERESTS_PROPERTY = "eventInterestProperty";
+
+ private TopicService topicService;
+
+ private EventService eventService;
+
+ private Double interestsMinValue;
+
+ private Double interestsMaxValue;
+
+ private Double interestsDividerValue;
+
+ @Override
+ @SuppressWarnings("unchecked")
+ public int execute(final Action action, final Event event) {
+ Map<String, Double> interestsAsMap = (Map<String, Double>) action.getParameterValues().get( ACTION_INTERESTS_PROPERTY );
+
+ if ( interestsAsMap == null ) {
+ interestsAsMap = (Map<String, Double>) event.getProperty( EVENT_INTERESTS_PROPERTY );
+
+ if (interestsAsMap == null) {
+ return EventService.NO_CHANGE;
+ }
+ }
+
+ final Profile profile = event.getProfile();
+
+ final Map<String, Double> profileInterestsMap = new HashMap<>();
+
+ if (profile.getProperty( EVENT_INTERESTS_PROPERTY ) != null) {
+ profileInterestsMap.putAll((Map<String, Double>) profile.getProperty( EVENT_INTERESTS_PROPERTY ));
+ }
+
+ interestsAsMap.forEach((topicId, incrementScoreBy) -> {
+ if (topicService.load(topicId) != null) {
+ if (!profileInterestsMap.containsKey(topicId)) {
+ profileInterestsMap.put(topicId, incrementScoreBy);
+ } else {
+ profileInterestsMap.merge(topicId, incrementScoreBy, Double::sum);
+ }
+
+ double value = Math.max(Math.min(profileInterestsMap.get(topicId), interestsMaxValue), interestsMinValue);
+
+ value = Math.min(value, interestsDividerValue) / interestsDividerValue;
+
+ profileInterestsMap.put(topicId, value);
+ } else {
+ LOG.debug("The interest with key \"{}\" was not recalculated for profile with itemId \"{}\" ", topicId, profile.getItemId());
+ }
+ });
+
+ final Map<String, Object> propertyToUpdate = new HashMap<>();
+ propertyToUpdate.put("properties.interests", profileInterestsMap);
+
+ final Event updatePropertiesEvent = new Event("updateProperties", null, profile, null, null, profile, new Date());
+ updatePropertiesEvent.setProperty("update", propertyToUpdate);
+
+ return eventService.send(updatePropertiesEvent);
+ }
+
+ public void setEventService(EventService eventService) {
+ this.eventService = eventService;
+ }
+
+ public void setTopicService(TopicService topicService) {
+ this.topicService = topicService;
+ }
+
+ public void setInterestsMinValue(Double interestsMinValue) {
+ this.interestsMinValue = interestsMinValue;
+ }
+
+ public void setInterestsMaxValue(Double interestsMaxValue) {
+ this.interestsMaxValue = interestsMaxValue;
+ }
+
+ public void setInterestsDividerValue(Double interestsDividerValue) {
+ this.interestsDividerValue = interestsDividerValue;
+ }
+
+}
diff --git a/plugins/baseplugin/src/main/resources/META-INF/cxs/actions/incrementInterest.json b/plugins/baseplugin/src/main/resources/META-INF/cxs/actions/incrementInterest.json
new file mode 100644
index 0000000..a328eb0
--- /dev/null
+++ b/plugins/baseplugin/src/main/resources/META-INF/cxs/actions/incrementInterest.json
@@ -0,0 +1,19 @@
+{
+ "metadata": {
+ "id": "incrementInterestAction",
+ "name": "incrementInterestAction",
+ "description": "",
+ "systemTags": [
+ "profileTags"
+ ],
+ "readOnly": true
+ },
+ "actionExecutor": "incrementInterest",
+ "parameters": [
+ {
+ "id": "eventInterestProperty",
+ "type": "properties",
+ "multivalued": false
+ }
+ ]
+}
diff --git a/plugins/baseplugin/src/main/resources/META-INF/cxs/conditions/incrementInterest.json b/plugins/baseplugin/src/main/resources/META-INF/cxs/conditions/incrementInterest.json
new file mode 100644
index 0000000..da1908a
--- /dev/null
+++ b/plugins/baseplugin/src/main/resources/META-INF/cxs/conditions/incrementInterest.json
@@ -0,0 +1,22 @@
+{
+ "metadata": {
+ "id": "incrementInterestEventCondition",
+ "name": "incrementInterestEventCondition",
+ "description": "",
+ "systemTags": [
+ "profileTags",
+ "event",
+ "condition",
+ "eventCondition"
+ ],
+ "readOnly": true
+ },
+ "parentCondition": {
+ "type": "eventTypeCondition",
+ "parameterValues": {
+ "eventTypeId": "incrementInterest"
+ }
+ },
+ "parameters": [
+ ]
+}
diff --git a/plugins/baseplugin/src/main/resources/META-INF/cxs/rules/incrementInterest.json b/plugins/baseplugin/src/main/resources/META-INF/cxs/rules/incrementInterest.json
new file mode 100644
index 0000000..ad002f3
--- /dev/null
+++ b/plugins/baseplugin/src/main/resources/META-INF/cxs/rules/incrementInterest.json
@@ -0,0 +1,21 @@
+{
+ "metadata": {
+ "id": "incrementInterest",
+ "name": "Update profile interests",
+ "description": "Update profile interests",
+ "readOnly": true
+ },
+ "condition": {
+ "type": "incrementInterestEventCondition",
+ "parameterValues": {
+ }
+ },
+ "actions": [
+ {
+ "type": "incrementInterestAction",
+ "parameterValues": {
+ "eventInterestProperty": "target.properties.interests"
+ }
+ }
+ ]
+}
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 9f7169a..ade153b 100644
--- a/plugins/baseplugin/src/main/resources/OSGI-INF/blueprint/blueprint.xml
+++ b/plugins/baseplugin/src/main/resources/OSGI-INF/blueprint/blueprint.xml
@@ -24,8 +24,8 @@
<cm:property-placeholder persistent-id="org.apache.unomi.plugins.base"
update-strategy="reload" placeholder-prefix="${base.">
<cm:default-properties>
- <cm:property name="useEventToUpdateProfile" value="false" />
- <cm:property name="usePropertyConditionOptimizations" value="true" />
+ <cm:property name="useEventToUpdateProfile" value="false"/>
+ <cm:property name="usePropertyConditionOptimizations" value="true"/>
</cm:default-properties>
</cm:property-placeholder>
@@ -38,14 +38,23 @@
</cm:default-properties>
</cm:property-placeholder>
+ <cm:property-placeholder persistent-id="org.apache.unomi.interests" update-strategy="reload">
+ <cm:default-properties>
+ <cm:property name="min_value" value="0.0"/>
+ <cm:property name="max_value" value="150.0"/>
+ <cm:property name="divider_value" value="100.0"/>
+ </cm:default-properties>
+ </cm:property-placeholder>
+
<reference id="definitionsService" interface="org.apache.unomi.api.services.DefinitionsService"/>
<reference id="persistenceService" interface="org.apache.unomi.persistence.spi.PersistenceService"/>
<reference id="profileService" interface="org.apache.unomi.api.services.ProfileService"/>
<reference id="privacyService" interface="org.apache.unomi.api.services.PrivacyService"/>
<reference id="segmentService" interface="org.apache.unomi.api.services.SegmentService"/>
<reference id="eventService" interface="org.apache.unomi.api.services.EventService"/>
- <reference id="configSharingService" interface="org.apache.unomi.api.services.ConfigSharingService" />
+ <reference id="configSharingService" interface="org.apache.unomi.api.services.ConfigSharingService"/>
<reference id="scriptExecutor" interface="org.apache.unomi.scripting.ScriptExecutor"/>
+ <reference id="topicService" interface="org.apache.unomi.api.services.TopicService"/>
<service
interface="org.apache.unomi.persistence.elasticsearch.conditions.ConditionESQueryBuilder">
@@ -104,7 +113,7 @@
<property name="definitionsService" ref="definitionsService"/>
<property name="persistenceService" ref="persistenceService"/>
<property name="segmentService" ref="segmentService"/>
- <property name="scriptExecutor" ref="scriptExecutor" />
+ <property name="scriptExecutor" ref="scriptExecutor"/>
<property name="maximumIdsQueryCount" value="${es.maximumIdsQueryCount}"/>
<property name="pastEventsDisablePartitions" value="${es.pastEventsDisablePartitions}"/>
<property name="aggregateQueryBucketSize" value="${es.aggregateQueryBucketSize}"/>
@@ -132,7 +141,7 @@
<entry key="conditionEvaluatorId" value="propertyConditionEvaluator"/>
</service-properties>
<bean class="org.apache.unomi.plugins.baseplugin.conditions.PropertyConditionEvaluator">
- <property name="usePropertyConditionOptimizations" value="${base.usePropertyConditionOptimizations}" />
+ <property name="usePropertyConditionOptimizations" value="${base.usePropertyConditionOptimizations}"/>
</bean>
</service>
@@ -167,7 +176,7 @@
<bean class="org.apache.unomi.plugins.baseplugin.conditions.PastEventConditionEvaluator">
<property name="definitionsService" ref="definitionsService"/>
<property name="persistenceService" ref="persistenceService"/>
- <property name="scriptExecutor" ref="scriptExecutor" />
+ <property name="scriptExecutor" ref="scriptExecutor"/>
</bean>
</service>
@@ -195,7 +204,7 @@
</service-properties>
<bean class="org.apache.unomi.plugins.baseplugin.actions.SetPropertyAction">
<property name="eventService" ref="eventService"/>
- <property name="useEventToUpdateProfile" value="${base.useEventToUpdateProfile}" />
+ <property name="useEventToUpdateProfile" value="${base.useEventToUpdateProfile}"/>
</bean>
</service>
@@ -262,7 +271,20 @@
<property name="persistenceService" ref="persistenceService"/>
<property name="definitionsService" ref="definitionsService"/>
<property name="privacyService" ref="privacyService"/>
- <property name="configSharingService" ref="configSharingService" />
+ <property name="configSharingService" ref="configSharingService"/>
+ </bean>
+ </service>
+
+ <service interface="org.apache.unomi.api.actions.ActionExecutor">
+ <service-properties>
+ <entry key="actionExecutorId" value="incrementInterest"/>
+ </service-properties>
+ <bean class="org.apache.unomi.plugins.baseplugin.actions.IncrementInterestAction">
+ <property name="eventService" ref="eventService"/>
+ <property name="topicService" ref="topicService"/>
+ <property name="interestsMinValue" value="${min_value}"/>
+ <property name="interestsMaxValue" value="${max_value}"/>
+ <property name="interestsDividerValue" value="${divider_value}"/>
</bean>
</service>