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>