You are viewing a plain text version of this content. The canonical link for it is here.
Posted to dev@unomi.apache.org by ta...@apache.org on 2021/03/29 14:02:08 UTC
[unomi] branch master updated: UNOMI-446 improve increment action
and make it generic (#265)
This is an automated email from the ASF dual-hosted git repository.
taybou 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 486aff8 UNOMI-446 improve increment action and make it generic (#265)
486aff8 is described below
commit 486aff8571694a9b57118fcb3425fa4f97cb0da3
Author: MT BENTERKI <be...@gmail.com>
AuthorDate: Mon Mar 29 16:00:20 2021 +0200
UNOMI-446 improve increment action and make it generic (#265)
---
.../test/java/org/apache/unomi/itests/AllITs.java | 2 +
.../test/java/org/apache/unomi/itests/BasicIT.java | 2 +-
.../apache/unomi/itests/IncrementPropertyIT.java | 424 +++++++++++++++++++++
.../actions/IncrementPropertyAction.java | 137 +++++++
.../META-INF/cxs/actions/incrementProperty.json | 29 ++
.../resources/OSGI-INF/blueprint/blueprint.xml | 6 +
.../services/actions/ActionExecutorDispatcher.java | 43 ++-
7 files changed, 633 insertions(+), 10 deletions(-)
diff --git a/itests/src/test/java/org/apache/unomi/itests/AllITs.java b/itests/src/test/java/org/apache/unomi/itests/AllITs.java
index bd36ac0..ea3312a 100644
--- a/itests/src/test/java/org/apache/unomi/itests/AllITs.java
+++ b/itests/src/test/java/org/apache/unomi/itests/AllITs.java
@@ -43,6 +43,8 @@ import org.junit.runners.Suite.SuiteClasses;
EventServiceIT.class,
PropertiesUpdateActionIT.class,
CopyPropertiesActionIT.class,
+ IncrementPropertyIT.class,
+ IncrementInterestsIT.class,
ModifyConsentIT.class,
PatchIT.class,
ContextServletIT.class,
diff --git a/itests/src/test/java/org/apache/unomi/itests/BasicIT.java b/itests/src/test/java/org/apache/unomi/itests/BasicIT.java
index 1f7299e..64286b8 100644
--- a/itests/src/test/java/org/apache/unomi/itests/BasicIT.java
+++ b/itests/src/test/java/org/apache/unomi/itests/BasicIT.java
@@ -72,7 +72,7 @@ public class BasicIT extends BaseIT {
private static final String ITEM_TYPE_SITE = "site";
private static final String ITEM_ID_SITE = "/test/site";
private static final String ITEM_TYPE_VISITOR = "VISITOR";
- private static final String ITEM_ID_PAGE_1 = "/test/site/page1";
+ protected static final String ITEM_ID_PAGE_1 = "/test/site/page1";
protected static final String ITEM_TYPE_PAGE = "page";
private static final String FIRST_NAME = "firstName";
diff --git a/itests/src/test/java/org/apache/unomi/itests/IncrementPropertyIT.java b/itests/src/test/java/org/apache/unomi/itests/IncrementPropertyIT.java
new file mode 100644
index 0000000..4488fb9
--- /dev/null
+++ b/itests/src/test/java/org/apache/unomi/itests/IncrementPropertyIT.java
@@ -0,0 +1,424 @@
+/*
+ * 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.*;
+
+import javax.inject.Inject;
+
+import org.apache.unomi.api.*;
+import org.apache.unomi.api.actions.Action;
+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;
+import org.junit.Before;
+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;
+
+import static org.apache.unomi.itests.BasicIT.ITEM_TYPE_PAGE;
+
+@RunWith(PaxExam.class)
+@ExamReactorStrategy(PerSuite.class)
+public class IncrementPropertyIT extends BaseIT {
+ private Profile profile;
+ private Rule rule;
+ private Event event;
+
+ @Inject
+ @Filter(timeout = 600000)
+ protected ProfileService profileService;
+
+ @Inject
+ @Filter(timeout = 600000)
+ protected EventService eventService;
+
+ @Inject
+ @Filter(timeout = 600000)
+ protected RulesService rulesService;
+
+ @Inject
+ @Filter(timeout = 600000)
+ protected DefinitionsService definitionsService;
+
+ @Before
+ public void setup() throws Exception {
+ profile = createProfile();
+ rule = new Rule();
+ }
+
+ @After
+ public void tearDown() {
+ rulesService.removeRule(rule.getItemId());
+ profileService.delete(profile.getItemId(), false);
+ }
+
+ @Test
+ public void testIncrementNotExistingPropertyWithDynamicName() throws InterruptedException {
+ int eventCode = buildActionAndSendEvent("pageView.${eventProperty::target.scope}", null, null, null);
+
+ if (eventCode == EventService.PROFILE_UPDATED) {
+ Profile updatedProfile = profileService.save(event.getProfile());
+ refreshPersistence();
+
+ int value = ((Map<String, Integer>) updatedProfile.getProperty("pageView")).get("acme-space");
+ Assert.assertEquals(1, value, 0.0);
+ } else {
+ Assert.fail("Profile was not updated");
+ }
+ }
+
+ @Test
+ public void testIncrementExistingPropertyWithDynamicName() throws InterruptedException {
+ Map<String, Object> properties = new HashMap<>();
+ Map<String, Integer> propertyValue = new HashMap<>();
+ propertyValue.put("acme-space", 24);
+ properties.put("pageView", propertyValue);
+
+ int eventCode = buildActionAndSendEvent("pageView.${eventProperty::target.scope}", null, properties, null);
+
+ if (eventCode == EventService.PROFILE_UPDATED) {
+ Profile updatedProfile = profileService.save(event.getProfile());
+
+ int value = ((Map<String, Integer>) updatedProfile.getProperty("pageView")).get("acme-space");
+ Assert.assertEquals(25, value, 0.0);
+ } else {
+ Assert.fail("Profile was not updated");
+ }
+ }
+
+ @Test
+ public void testIncrementNotExistingProperty() throws InterruptedException {
+ int eventCode = buildActionAndSendEvent("pageView.acme", null, null, null);
+
+ if (eventCode == EventService.PROFILE_UPDATED) {
+ Profile updatedProfile = profileService.save(event.getProfile());
+
+ int value = ((Map<String, Integer>) updatedProfile.getProperty("pageView")).get("acme");
+ Assert.assertEquals(1, value, 0.0);
+ } else {
+ Assert.fail("Profile was not updated");
+ }
+ }
+
+ @Test
+ public void testIncrementExistingProperty() throws InterruptedException {
+ Map<String, Object> properties = new HashMap<>();
+ Map<String, Integer> propertyValue = new HashMap<>();
+ propertyValue.put("acme", 49);
+ properties.put("pageView", propertyValue);
+
+ int eventCode = buildActionAndSendEvent("pageView.acme", null, properties, null);
+
+ if (eventCode == EventService.PROFILE_UPDATED) {
+ Profile updatedProfile = profileService.save(event.getProfile());
+
+ int value = ((Map<String, Integer>) updatedProfile.getProperty("pageView")).get("acme");
+ Assert.assertEquals(50, value, 0.0);
+ } else {
+ Assert.fail("Profile was not updated");
+ }
+ }
+
+ @Test
+ public void testIncrementExistingPropertyWithExistingEventProperty() throws InterruptedException {
+ Map<String, Object> properties = new HashMap<>();
+ Map<String, Integer> propertyValue = new HashMap<>();
+ propertyValue.put("acme", 49);
+ properties.put("pageView", propertyValue);
+
+ Map<String, Object> targetProperties = new HashMap<>();
+ propertyValue = new HashMap<>();
+ propertyValue.put("nasa", 19);
+ targetProperties.put("project", propertyValue);
+
+ int eventCode = buildActionAndSendEvent("pageView.acme", "project.nasa", properties, targetProperties);
+
+ if (eventCode == EventService.PROFILE_UPDATED) {
+ Profile updatedProfile = profileService.save(event.getProfile());
+
+ int value = ((Map<String, Integer>) updatedProfile.getProperty("pageView")).get("acme");
+ Assert.assertEquals(68, value, 0.0);
+ } else {
+ Assert.fail("Profile was not updated");
+ }
+ }
+
+ @Test
+ public void testIncrementNotExistingObjectPropertyWithExistingEventObjectProperty() throws InterruptedException {
+ Map<String, Object> targetProperties = new HashMap<>();
+ Map<String, Integer> propertyValue = new HashMap<>();
+ propertyValue.put("acme", 49);
+ propertyValue.put("health", 18);
+ propertyValue.put("sport", 99);
+ targetProperties.put("pageView", propertyValue);
+
+ int eventCode = buildActionAndSendEvent("pageView", "pageView", null, targetProperties);
+
+ if (eventCode == EventService.PROFILE_UPDATED) {
+ Profile updatedProfile = profileService.save(event.getProfile());
+
+ Map<String, Integer> property = ((Map<String, Integer>) updatedProfile.getProperty("pageView"));
+ Assert.assertEquals(49, property.get("acme"), 0.0);
+ Assert.assertEquals(18, property.get("health"), 0.0);
+ Assert.assertEquals(99, property.get("sport"), 0.0);
+ } else {
+ Assert.fail("Profile was not updated");
+ }
+ }
+
+ @Test
+ public void testIncrementExistingObjectProperty() throws InterruptedException {
+ Map<String, Object> properties = new HashMap<>();
+ Map<String, Integer> propertyValue = new HashMap<>();
+ propertyValue.put("acme", 49);
+ propertyValue.put("nasa", 5);
+ properties.put("pageView", propertyValue);
+
+ int eventCode = buildActionAndSendEvent("pageView", null, properties, null);
+
+ if (eventCode == EventService.PROFILE_UPDATED) {
+ Profile updatedProfile = profileService.save(event.getProfile());
+
+ Map<String, Integer> property = ((Map<String, Integer>) updatedProfile.getProperty("pageView"));
+ Assert.assertEquals(50, property.get("acme"), 0.0);
+ Assert.assertEquals(6, property.get("nasa"), 0.0);
+ } else {
+ Assert.fail("Profile was not updated");
+ }
+ }
+
+ @Test
+ public void testIncrementExistingObjectPropertyWithExistingEventObjectProperty() throws InterruptedException {
+ Map<String, Object> properties = new HashMap<>();
+ Map<String, Integer> propertyValue = new HashMap<>();
+ propertyValue.put("acme", 49);
+ properties.put("pageView", propertyValue);
+
+ Map<String, Object> targetProperties = new HashMap<>();
+ Map<String, Integer> propertyValue1 = new HashMap<>();
+ propertyValue1.put("acme", 31);
+ propertyValue1.put("health", 88);
+ propertyValue1.put("sport", 9);
+ targetProperties.put("pageView", propertyValue1);
+
+ int eventCode = buildActionAndSendEvent("pageView", "pageView", properties, targetProperties);
+
+ if (eventCode == EventService.PROFILE_UPDATED) {
+ Profile updatedProfile = profileService.save(event.getProfile());
+
+ Map<String, Integer> property = ((Map<String, Integer>) updatedProfile.getProperty("pageView"));
+ Assert.assertEquals(80, property.get("acme"), 0.0);
+ Assert.assertEquals(88, property.get("health"), 0.0);
+ Assert.assertEquals(9, property.get("sport"), 0.0);
+ } else {
+ Assert.fail("Profile was not updated");
+ }
+ }
+
+ @Test
+ public void testIncrementExistingPropertyNested() throws InterruptedException {
+ Map<String, Object> properties = new HashMap<>();
+ Map<String, Object> properties1 = new HashMap<>();
+ Map<String, Object> properties2 = new HashMap<>();
+ Map<String, Integer> propertyValue = new HashMap<>();
+ propertyValue.put("city", 13);
+ properties2.put("state", propertyValue);
+ properties1.put("country", properties2);
+ properties.put("continent", properties1);
+
+ int eventCode = buildActionAndSendEvent("continent.country.state.city", null, properties, null);
+
+ if (eventCode == EventService.PROFILE_UPDATED) {
+ Profile updatedProfile = profileService.save(event.getProfile());
+
+ Map<String, Integer> property = (Map<String, Integer>) ((Map<String, Object>) ((Map<String, Object>) updatedProfile.getProperty("continent")).get("country")).get("state");
+ Assert.assertEquals(14, property.get("city"), 0.0);
+ } else {
+ Assert.fail("Profile was not updated");
+ }
+ }
+
+ @Test
+ public void testIncrementNotExistingPropertyNested() throws InterruptedException {
+ int eventCode = buildActionAndSendEvent("continent.country.state.city", null, null, null);
+
+ if (eventCode == EventService.PROFILE_UPDATED) {
+ Profile updatedProfile = profileService.save(event.getProfile());
+
+ Map<String, Integer> property = (Map<String, Integer>) ((Map<String, Object>) ((Map<String, Object>) updatedProfile.getProperty("continent")).get("country")).get("state");
+ Assert.assertEquals(1, property.get("city"), 0.0);
+ } else {
+ Assert.fail("Profile was not updated");
+ }
+ }
+
+ @Test
+ public void testIncrementExistingPropertyNestedWithExistingEventProperty() throws InterruptedException {
+ Map<String, Object> properties = new HashMap<>();
+ Map<String, Object> properties1 = new HashMap<>();
+ Map<String, Object> properties2 = new HashMap<>();
+ Map<String, Integer> propertyValue = new HashMap<>();
+ propertyValue.put("city", 13);
+ properties2.put("state", propertyValue);
+ properties1.put("country", properties2);
+ properties.put("continent", properties1);
+
+ Map<String, Object> targetProperties = new HashMap<>();
+ Map<String, Object> properties3 = new HashMap<>();
+ Map<String, Object> propertyValue1 = new HashMap<>();
+ propertyValue1.put("zone", 107);
+ properties3.put("mars", propertyValue1);
+ targetProperties.put("planet", properties3);
+
+ int eventCode = buildActionAndSendEvent("continent.country.state.city", "planet.mars.zone", properties, targetProperties);
+
+ if (eventCode == EventService.PROFILE_UPDATED) {
+ Profile updatedProfile = profileService.save(event.getProfile());
+
+ Map<String, Integer> property = (Map<String, Integer>) ((Map<String, Object>) ((Map<String, Object>) updatedProfile.getProperty("continent")).get("country")).get("state");
+ Assert.assertEquals(120, property.get("city"), 0.0);
+ } else {
+ Assert.fail("Profile was not updated");
+ }
+ }
+
+ @Test
+ public void testIncrementObjectPropertyContainsStringValue() throws InterruptedException {
+ Map<String, Object> properties = new HashMap<>();
+ Map<String, Object> propertyValue = new HashMap<>();
+ propertyValue.put("books", 59);
+ propertyValue.put("chapters", 1001);
+ propertyValue.put("featured", "The forty rules");
+ properties.put("library", propertyValue);
+
+ int eventCode = buildActionAndSendEvent("library", null, properties, null);
+
+ if (eventCode == EventService.PROFILE_UPDATED) {
+ Profile updatedProfile = profileService.save(event.getProfile());
+
+ Map<String, Object> property = ((Map<String, Object>) updatedProfile.getProperty("library"));
+ Assert.assertEquals(60, (int) property.get("books"), 0.0);
+ Assert.assertEquals(1002, (int) property.get("chapters"), 0.0);
+ Assert.assertEquals("The forty rules", property.get("featured"));
+ } else {
+ Assert.fail("Profile was not updated");
+ }
+ }
+
+ @Test
+ public void testIncrementObjectPropertyContainsStringValueWithExistingEventProperty() throws InterruptedException {
+ Map<String, Object> properties = new HashMap<>();
+ Map<String, Object> propertyValue = new HashMap<>();
+ propertyValue.put("books", 59);
+ propertyValue.put("chapters", 1001);
+ propertyValue.put("featured", "The forty rules");
+ properties.put("library", propertyValue);
+
+ Map<String, Object> targetProperties = new HashMap<>();
+ Map<String, Object> properties1 = new HashMap<>();
+ Map<String, Object> propertyValue1 = new HashMap<>();
+ propertyValue1.put("books", 222);
+ propertyValue1.put("chapters", 2048);
+ propertyValue1.put("featured", "Bible");
+ properties1.put("library", propertyValue1);
+ targetProperties.put("main", properties1);
+
+ int eventCode = buildActionAndSendEvent("library", "main.library", properties, targetProperties);
+
+ if (eventCode == EventService.PROFILE_UPDATED) {
+ Profile updatedProfile = profileService.save(event.getProfile());
+
+ Map<String, Object> property = ((Map<String, Object>) updatedProfile.getProperty("library"));
+ Assert.assertEquals(281, (int) property.get("books"), 0.0);
+ Assert.assertEquals(3049, (int) property.get("chapters"), 0.0);
+ Assert.assertEquals("The forty rules", property.get("featured"));
+ } else {
+ Assert.fail("Profile was not updated");
+ }
+ }
+
+ private void createRule(Action incrementPropertyAction) throws InterruptedException {
+ Condition condition = createCondition();
+ Metadata metadata = createMetadata();
+
+ List<Action> actions = new ArrayList<>();
+ actions.add(incrementPropertyAction);
+
+ rule.setCondition(condition);
+ rule.setActions(actions);
+ rule.setMetadata(metadata);
+ rulesService.setRule(rule);
+ refreshPersistence();
+ }
+
+ private int buildActionAndSendEvent(String propertyName, String propertyTargetName, Map<String, Object> properties, Map<String, Object> targetProperties) throws InterruptedException {
+ Action incrementPropertyAction = new Action(definitionsService.getActionType("incrementPropertyAction"));
+ incrementPropertyAction.setParameter("propertyName", propertyName);
+ if (propertyTargetName != null) incrementPropertyAction.setParameter("propertyTarget", propertyTargetName);
+
+ createRule(incrementPropertyAction);
+
+ if (properties != null) profile.setProperties(properties);
+
+ CustomItem target = new CustomItem("ITEM_ID_PAGE", ITEM_TYPE_PAGE);
+ target.setScope("acme-space");
+ if (targetProperties != null) target.setProperties(targetProperties);
+
+ event = new Event("view", null, profile, null, null, target, new Date());
+ event.setPersistent(false);
+
+ int eventCode = eventService.send(event);
+ refreshPersistence();
+
+ return eventCode;
+ }
+
+ private Metadata createMetadata() {
+ String itemId = UUID.randomUUID().toString();
+ Metadata metadata = new Metadata();
+ metadata.setId(itemId);
+ metadata.setName(itemId);
+ metadata.setDescription(itemId);
+ metadata.setEnabled(true);
+ metadata.setScope("systemscope");
+ return metadata;
+ }
+
+ private Condition createCondition() {
+ Condition condition = new Condition(definitionsService.getConditionType("eventTypeCondition"));
+ condition.setParameter("eventTypeId", "view");
+ return condition;
+ }
+
+ private Profile createProfile() throws InterruptedException {
+ Profile profile = new Profile(UUID.randomUUID().toString());
+
+ profileService.save(profile);
+ refreshPersistence();
+
+ return profile;
+ }
+}
diff --git a/plugins/baseplugin/src/main/java/org/apache/unomi/plugins/baseplugin/actions/IncrementPropertyAction.java b/plugins/baseplugin/src/main/java/org/apache/unomi/plugins/baseplugin/actions/IncrementPropertyAction.java
new file mode 100644
index 0000000..3651ece
--- /dev/null
+++ b/plugins/baseplugin/src/main/java/org/apache/unomi/plugins/baseplugin/actions/IncrementPropertyAction.java
@@ -0,0 +1,137 @@
+/*
+ * 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 org.apache.commons.beanutils.PropertyUtils;
+import org.apache.commons.lang3.StringUtils;
+import org.apache.unomi.api.*;
+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.persistence.spi.PropertyHelper;
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
+
+import java.lang.reflect.InvocationTargetException;
+import java.util.Map;
+
+public class IncrementPropertyAction implements ActionExecutor {
+ private static final Logger logger = LoggerFactory.getLogger(IncrementPropertyAction.class.getName());
+
+ @Override
+ public int execute(final Action action, final Event event) {
+ boolean storeInSession = Boolean.TRUE.equals(action.getParameterValues().get("storeInSession"));
+ if (storeInSession && event.getSession() == null) {
+ return EventService.NO_CHANGE;
+ }
+
+ String propertyName = (String) action.getParameterValues().get("propertyName");
+ Profile profile = event.getProfile();
+ Session session = event.getSession();
+
+ try {
+ Map<String, Object> properties = storeInSession ? session.getProperties() : profile.getProperties();
+ Object propertyValue = getPropertyValue(action, event, propertyName, properties);
+ if (PropertyHelper.setProperty(properties, propertyName, propertyValue, "alwaysSet")) {
+ return storeInSession ? EventService.SESSION_UPDATED : EventService.PROFILE_UPDATED;
+ }
+ } catch (IllegalAccessException | NoSuchMethodException | InvocationTargetException e) {
+ logger.warn("Error resolving nested property of object. See debug log level for more information");
+ if (logger.isDebugEnabled()) {
+ logger.debug("Error resolving nested property of item: {}", storeInSession ? session : profile, e);
+ }
+ } catch (IllegalStateException ee) {
+ logger.warn("Error increment existing property, because existing property doesn't have expected type. See debug log level for more information");
+ if (logger.isDebugEnabled()) {
+ logger.debug(ee.getMessage(), ee);
+ }
+ }
+
+ return EventService.NO_CHANGE;
+ }
+
+ private Object getPropertyValue(Action action, Event event, String propertyName, Map<String, Object> properties)
+ throws IllegalAccessException, InvocationTargetException, NoSuchMethodException {
+ String propertyTarget = (String) action.getParameterValues().get("propertyTarget");
+ String rootPropertyName = propertyName.split("\\.")[0];
+ Object propertyValue = 1;
+
+ Object propertyTargetValue = null;
+
+ if (StringUtils.isNotEmpty(propertyTarget)) {
+ propertyTargetValue = PropertyUtils.getNestedProperty(((CustomItem) event.getTarget()).getProperties(), propertyTarget);
+ }
+
+ if (propertyTargetValue != null) {
+ if (propertyTargetValue instanceof Integer) {
+ if (properties.containsKey(rootPropertyName)) {
+ Object nestedProperty = PropertyUtils.getNestedProperty(properties, propertyName);
+ if (nestedProperty == null) {
+ propertyValue = propertyTargetValue;
+ } else if (nestedProperty instanceof Integer) {
+ propertyValue = (int) propertyTargetValue + (int) nestedProperty;
+ } else {
+ throw new IllegalStateException("Property: " + propertyName + " already exist, can not increment the property because the exiting property is not integer");
+ }
+ } else {
+ propertyValue = propertyTargetValue;
+ }
+ } else if (propertyTargetValue instanceof Map) {
+ if (properties.containsKey(rootPropertyName)) {
+ Object nestedPropertyValue = PropertyUtils.getNestedProperty(properties, propertyName);
+ if (nestedPropertyValue == null) {
+ propertyValue = propertyTargetValue;
+ } else if (nestedPropertyValue instanceof Map) {
+ Map<String, Object> nestedProperty = (Map<String, Object>) nestedPropertyValue;
+
+ ((Map<String, Object>) propertyTargetValue).forEach((key, targetValue) -> {
+ if ((targetValue instanceof Integer && (nestedProperty.containsKey(key) && nestedProperty.get(key) instanceof Integer)) ||
+ (targetValue instanceof Integer && !nestedProperty.containsKey(key))) {
+ nestedProperty.put(key, nestedProperty.containsKey(key) ? (int) nestedProperty.get(key) + (int) targetValue : targetValue);
+ }
+ });
+ propertyValue = nestedProperty;
+ } else {
+ throw new IllegalStateException("Property: " + propertyName + " already exist, can not increment the properties from the map because the exiting property is not map");
+ }
+ } else {
+ propertyValue = propertyTargetValue;
+ }
+ }
+ } else {
+ if (properties.containsKey(rootPropertyName)) {
+ Object nestedProperty = PropertyUtils.getNestedProperty(properties, propertyName);
+ if (nestedProperty == null) {
+ propertyValue = 1;
+ } else if (nestedProperty instanceof Integer) {
+ propertyValue = (int) nestedProperty + 1;
+ } else if (nestedProperty instanceof Map) {
+ ((Map<String, Object>) nestedProperty).forEach((key, propValue) -> {
+ if (propValue instanceof Integer) {
+ ((Map<String, Integer>) nestedProperty).merge(key, 1, Integer::sum);
+ }
+ });
+ propertyValue = nestedProperty;
+ } else {
+ throw new IllegalStateException("Property: " + propertyName + " already exist, can not increment the property because the exiting property is not integer or map");
+ }
+ }
+ }
+
+ return propertyValue;
+ }
+}
diff --git a/plugins/baseplugin/src/main/resources/META-INF/cxs/actions/incrementProperty.json b/plugins/baseplugin/src/main/resources/META-INF/cxs/actions/incrementProperty.json
new file mode 100644
index 0000000..3ba8828
--- /dev/null
+++ b/plugins/baseplugin/src/main/resources/META-INF/cxs/actions/incrementProperty.json
@@ -0,0 +1,29 @@
+{
+ "metadata": {
+ "id": "incrementPropertyAction",
+ "name": "incrementPropertyAction",
+ "description": "",
+ "systemTags": [
+ "profileTags"
+ ],
+ "readOnly": true
+ },
+ "actionExecutor": "incrementProperty",
+ "parameters": [
+ {
+ "id": "propertyName",
+ "type": "string",
+ "multivalued": false
+ },
+ {
+ "id": "propertyTarget",
+ "type": "string",
+ "multivalued": false
+ },
+ {
+ "id": "storeInSession",
+ "type": "boolean",
+ "multivalued": false
+ }
+ ]
+}
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 48c4b46..61d0027 100644
--- a/plugins/baseplugin/src/main/resources/OSGI-INF/blueprint/blueprint.xml
+++ b/plugins/baseplugin/src/main/resources/OSGI-INF/blueprint/blueprint.xml
@@ -298,4 +298,10 @@
</bean>
</service>
+ <service interface="org.apache.unomi.api.actions.ActionExecutor">
+ <service-properties>
+ <entry key="actionExecutorId" value="incrementProperty"/>
+ </service-properties>
+ <bean class="org.apache.unomi.plugins.baseplugin.actions.IncrementPropertyAction"/>
+ </service>
</blueprint>
diff --git a/services/src/main/java/org/apache/unomi/services/actions/ActionExecutorDispatcher.java b/services/src/main/java/org/apache/unomi/services/actions/ActionExecutorDispatcher.java
index e0c29f5..99aefa7 100644
--- a/services/src/main/java/org/apache/unomi/services/actions/ActionExecutorDispatcher.java
+++ b/services/src/main/java/org/apache/unomi/services/actions/ActionExecutorDispatcher.java
@@ -40,6 +40,8 @@ import java.util.concurrent.ConcurrentHashMap;
public class ActionExecutorDispatcher {
private static final Logger logger = LoggerFactory.getLogger(ActionExecutorDispatcher.class.getName());
private static final String VALUE_NAME_SEPARATOR = "::";
+ private static final String PLACEHOLDER_PREFIX = "${";
+ private static final String PLACEHOLDER_SUFFIX = "}";
private final Map<String, ValueExtractor> valueExtractors = new HashMap<>(11);
private Map<String, ActionExecutor> executors = new ConcurrentHashMap<>();
private MetricsService metricsService;
@@ -124,13 +126,21 @@ public class ActionExecutorDispatcher {
if (value instanceof String) {
String s = (String) value;
try {
- // check if we have special values
- if (s.contains(VALUE_NAME_SEPARATOR)) {
- final String valueType = StringUtils.substringBefore(s, VALUE_NAME_SEPARATOR);
- final String valueAsString = StringUtils.substringAfter(s, VALUE_NAME_SEPARATOR);
- final ValueExtractor extractor = valueExtractors.get(valueType);
- if (extractor != null) {
- value = extractor.extract(valueAsString, event);
+ if (s.contains(PLACEHOLDER_PREFIX)) {
+ while (s.contains(PLACEHOLDER_PREFIX)) {
+ String substring = s.substring(s.indexOf(PLACEHOLDER_PREFIX) + 2, s.indexOf(PLACEHOLDER_SUFFIX));
+ Object v = extractValue(substring, event);
+ if (v != null) {
+ s = s.replace(PLACEHOLDER_PREFIX + substring + PLACEHOLDER_SUFFIX, v.toString());
+ } else {
+ break;
+ }
+ }
+ value = s;
+ } else {
+ // check if we have special values
+ if (s.contains(VALUE_NAME_SEPARATOR)) {
+ value = extractValue(s, event);
}
}
} catch (UnsupportedOperationException e) {
@@ -146,13 +156,28 @@ public class ActionExecutorDispatcher {
return values;
}
+ private Object extractValue(String s, Event event) throws IllegalAccessException, NoSuchMethodException, InvocationTargetException {
+ Object value = null;
+
+ String valueType = StringUtils.substringBefore(s, VALUE_NAME_SEPARATOR);
+ String valueAsString = StringUtils.substringAfter(s, VALUE_NAME_SEPARATOR);
+ ValueExtractor extractor = valueExtractors.get(valueType);
+ if (extractor != null) {
+ value = extractor.extract(valueAsString, event);
+ }
+
+ return value;
+ }
+
@SuppressWarnings("unchecked")
private boolean hasContextualParameter(Map<String, Object> values) {
for (Map.Entry<String, Object> entry : values.entrySet()) {
Object value = entry.getValue();
if (value instanceof String) {
String s = (String) value;
- if (s.contains(VALUE_NAME_SEPARATOR) && valueExtractors.containsKey(StringUtils.substringBefore(s, VALUE_NAME_SEPARATOR))) {
+ String str = s.contains(PLACEHOLDER_PREFIX) ? s.substring(s.indexOf(PLACEHOLDER_PREFIX) + 2, s.indexOf(PLACEHOLDER_SUFFIX)) : s;
+
+ if (str.contains(VALUE_NAME_SEPARATOR) && valueExtractors.containsKey(StringUtils.substringBefore(str, VALUE_NAME_SEPARATOR))) {
return true;
}
} else if (value instanceof Map) {
@@ -173,7 +198,7 @@ public class ActionExecutorDispatcher {
int colonPos = actionKey.indexOf(":");
if (colonPos > 0) {
String actionPrefix = actionKey.substring(0, colonPos);
- String actionName = actionKey.substring(colonPos+1);
+ String actionName = actionKey.substring(colonPos + 1);
ActionDispatcher actionDispatcher = actionDispatchers.get(actionPrefix);
if (actionDispatcher == null) {
logger.warn("Couldn't find any action dispatcher for prefix '{}', action {} won't execute !", actionPrefix, actionKey);