UNOMI-411 Bug fix in profile property condition and add getNestedProperty method (#229)

* add nested property and fix bug in property condition evaluator for exists operator

* Support direct property from getNestedProperty

* Add itest
diff --git a/api/pom.xml b/api/pom.xml
index 5394ff1..0bc24e4 100644
--- a/api/pom.xml
+++ b/api/pom.xml
@@ -37,7 +37,10 @@
             <version>2.2.11</version>
             <scope>provided</scope>
         </dependency>
-
+        <dependency>
+            <groupId>org.apache.commons</groupId>
+            <artifactId>commons-lang3</artifactId>
+        </dependency>
     </dependencies>
 
     <reporting>
diff --git a/api/src/main/java/org/apache/unomi/api/Event.java b/api/src/main/java/org/apache/unomi/api/Event.java
index e7ff5a6..6b4e1e8 100644
--- a/api/src/main/java/org/apache/unomi/api/Event.java
+++ b/api/src/main/java/org/apache/unomi/api/Event.java
@@ -17,6 +17,7 @@
 
 package org.apache.unomi.api;
 
+import org.apache.commons.lang3.StringUtils;
 import org.apache.unomi.api.actions.ActionPostExecutor;
 
 import javax.xml.bind.annotation.XmlTransient;
@@ -324,6 +325,30 @@
     }
 
     /**
+     * Retrieves the value of the nested property identified by the specified name.
+     *
+     * @param name the name of the property to be retrieved, splited in the nested properties with "."
+     * @return the value of the property identified by the specified name
+     */
+    public Object getNestedProperty(String name) {
+        if (!name.contains(".")) {
+            return getProperty(name);
+        }
+
+        Map properties = this.properties;
+        String[] propertyPath = StringUtils.substringBeforeLast(name, ".").split("\\.");
+        String propertyName = StringUtils.substringAfterLast(name, ".");
+
+        for (String property: propertyPath) {
+            properties = (Map) properties.get(property);
+            if (properties == null) {
+                return null;
+            }
+        }
+        return properties.get(propertyName);
+    }
+
+    /**
      * Retrieves the properties.
      *
      * @return the properties
diff --git a/api/src/main/java/org/apache/unomi/api/Profile.java b/api/src/main/java/org/apache/unomi/api/Profile.java
index ba75da9..7115bd5 100644
--- a/api/src/main/java/org/apache/unomi/api/Profile.java
+++ b/api/src/main/java/org/apache/unomi/api/Profile.java
@@ -17,6 +17,7 @@
 
 package org.apache.unomi.api;
 
+import org.apache.commons.lang3.StringUtils;
 import org.apache.unomi.api.segments.Scoring;
 import org.apache.unomi.api.segments.Segment;
 
@@ -95,6 +96,30 @@
     }
 
     /**
+     * Retrieves the value of the nested property identified by the specified name.
+     *
+     * @param name the name of the property to be retrieved, splited in the nested properties with "."
+     * @return the value of the property identified by the specified name
+     */
+    public Object getNestedProperty(String name) {
+        if (!name.contains(".")) {
+            return getProperty(name);
+        }
+
+        Map properties = this.properties;
+        String[] propertyPath = StringUtils.substringBeforeLast(name, ".").split("\\.");
+        String propertyName = StringUtils.substringAfterLast(name, ".");
+
+        for (String property: propertyPath) {
+            properties = (Map) properties.get(property);
+            if (properties == null) {
+                return null;
+            }
+        }
+        return properties.get(propertyName);
+    }
+
+    /**
      * Retrieves a Map of all property name - value pairs for this profile.
      *
      * @return a Map of all property name - value pairs for this profile
diff --git a/itests/src/test/java/org/apache/unomi/itests/EventServiceIT.java b/itests/src/test/java/org/apache/unomi/itests/EventServiceIT.java
index 530baa8..9f3eb05 100644
--- a/itests/src/test/java/org/apache/unomi/itests/EventServiceIT.java
+++ b/itests/src/test/java/org/apache/unomi/itests/EventServiceIT.java
@@ -38,6 +38,8 @@
 
 import javax.inject.Inject;
 import java.util.Date;
+import java.util.HashMap;
+import java.util.Map;
 
 import java.text.SimpleDateFormat;
 import java.text.ParseException;
@@ -46,6 +48,7 @@
 import org.junit.Assert;
 
 
+
 /**
  * An integration test for the event service
  */
@@ -150,4 +153,20 @@
         Assert.assertEquals(0, profiles.getList().size());
     }
 
+    @Test
+    public void test_EventGetNestedProperty() {
+        String nestedProperty = "outerProperty.innerProperty";
+        String testValue = "test-value";
+        String eventId = "test-event-id-" + System.currentTimeMillis();
+        String profileId = "test-profile-id";
+        String eventType = "test-type";
+        Profile profile = new Profile(profileId);
+        Event event = new Event(eventId, eventType, null, profile, null, null, null, new Date());
+        final Map<String, String> innerProperty = new HashMap<>();
+        innerProperty.put("innerProperty", testValue);
+        event.setProperty("outerProperty", innerProperty);
+        String value = (String) event.getNestedProperty(nestedProperty);
+        Assert.assertEquals(testValue, value);
+    }
+
 }
diff --git a/itests/src/test/java/org/apache/unomi/itests/ProfileServiceIT.java b/itests/src/test/java/org/apache/unomi/itests/ProfileServiceIT.java
index 2d0ad9a..25788bd 100644
--- a/itests/src/test/java/org/apache/unomi/itests/ProfileServiceIT.java
+++ b/itests/src/test/java/org/apache/unomi/itests/ProfileServiceIT.java
@@ -37,6 +37,9 @@
 import org.slf4j.Logger;
 import org.slf4j.LoggerFactory;
 
+import java.util.HashMap;
+import java.util.Map;
+
 import javax.inject.Inject;
 
 /**
@@ -136,5 +139,18 @@
         }
     }
 
+    @Test
+    public void test_EventGetNestedProperty() {
+        String nestedProperty = "outerProperty.innerProperty";
+        String testValue = "test-value";
+        String profileId = "test-profile-id";
+        Profile profile = new Profile(profileId);
+        final Map<String, String> innerProperty = new HashMap<>();
+        innerProperty.put("innerProperty", testValue);
+        profile.setProperty("outerProperty", innerProperty);
+        String value = (String) profile.getNestedProperty(nestedProperty);
+        assertEquals(testValue, value);
+    }
+
 
 }
diff --git a/plugins/baseplugin/src/main/java/org/apache/unomi/plugins/baseplugin/conditions/PropertyConditionEvaluator.java b/plugins/baseplugin/src/main/java/org/apache/unomi/plugins/baseplugin/conditions/PropertyConditionEvaluator.java
index 13319b6..6514f04 100644
--- a/plugins/baseplugin/src/main/java/org/apache/unomi/plugins/baseplugin/conditions/PropertyConditionEvaluator.java
+++ b/plugins/baseplugin/src/main/java/org/apache/unomi/plugins/baseplugin/conditions/PropertyConditionEvaluator.java
@@ -199,6 +199,9 @@
         } else if (actualValue == null) {
             return op.equals("missing");
         } else if (op.equals("exists")) {
+            if (actualValue instanceof List) {
+                return ((List) actualValue).size() > 0;
+            }
             return true;
         } else if (op.equals("equals")) {
             if (actualValue instanceof Collection) {