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 6428130  [UNOMI-512] Custom item types (#344)
6428130 is described below

commit 64281306c770c710ec737f64ece7332f54e91cd5
Author: Serge Huber <[email protected]>
AuthorDate: Mon Nov 8 11:12:23 2021 +0100

    [UNOMI-512] Custom item types (#344)
    
    * Cleanup rule
    
    * Support for custom items:
    - Made it possible to declare new custom item types that will inherit from 
the Item class and be loaded as CustomItem instances, but that can have any 
structure in the properties map. Custom item types have a "subtype" that 
defines its unique type and that is used to store the custom item objects in 
seperate indices in the persistence service. It is also possible to query 
custom item types using new methods in the PersistenceService SPI.
    Moved the ParserHelper into the API
    - The ParserHelper is very useful to load conditions and apply visitors on 
them, so it was moved to the API in order to be easier to re-use in multiple 
subsystems or plugins.
    Moved the ValueExtractors into the ParserHelper
    - The ValueExtractors used in the ActionExecutorDispatcherImpl have been 
moved into the ParserHelper so that they can be re-used more easily, for 
example to use them in other places such as conditions or other objects. The 
script value extractor, that has security issues is not part of the default set 
of value extractors and is currently only added in the 
ActionExecutorDispatcherImpl.
    Make is possible to add custom ItemDeserializers from plugins
    - It is now possible for plugins to add their own Item deserializers in the 
CustomObjectMapper using new methods to register mappings. This can be used by 
plugins to add new Item types and is used by the new custom item type subsystem.
    
    * Event type modifications for custom items
    
    * Modify property condition builders to use collections instead of lists 
(works with sets now)
    
    * Modify property condition builders to use collections instead of lists 
(works with sets now)
    
    * JSON Schema integration
    
    * JSON Schema integration
    
    * Fix merge issue
    
    * Change the identifier limit to 60 instead of 50
---
 api/pom.xml                                        |   8 ++
 .../main/java/org/apache/unomi/api/CustomItem.java |   9 ++
 .../java/org/apache/unomi/api/MetadataItem.java    |   4 +-
 .../org/apache/unomi/api/utils}/ParserHelper.java  | 112 +++++++++++++++-
 itests/pom.xml                                     |   2 +-
 .../test/java/org/apache/unomi/itests/BasicIT.java |   5 +
 itests/src/test/resources/testLogin.json           |   6 +-
 .../ElasticSearchPersistenceServiceImpl.java       | 142 +++++++++++++++++----
 .../conditions/ConditionContextHelper.java         |  10 +-
 .../unomi/persistence/spi/CustomObjectMapper.java  |  60 ++++++---
 .../unomi/persistence/spi/ItemDeserializer.java    |   7 +
 .../unomi/persistence/spi/PersistenceService.java  |  51 ++++++++
 .../spi/PropertyTypedObjectDeserializer.java       |  11 ++
 .../PropertyConditionESQueryBuilder.java           |  24 ++--
 .../conditions/PropertyConditionEvaluator.java     |  69 +++++++---
 .../actions/impl/ActionExecutorDispatcherImpl.java | 133 +------------------
 .../impl/definitions/DefinitionsServiceImpl.java   |   2 +-
 .../services/impl/events/EventServiceImpl.java     |   2 +-
 .../services/impl/goals/GoalsServiceImpl.java      |   2 +-
 .../services/impl/profiles/ProfileServiceImpl.java |   2 +-
 .../services/impl/queries/QueryServiceImpl.java    |   2 +-
 .../services/impl/rules/RulesServiceImpl.java      |   2 +-
 .../services/impl/segments/SegmentServiceImpl.java |   2 +-
 .../META-INF/cxs/events/anonymizeProfile.json      |   4 +
 .../META-INF/cxs/events/articleCompleted.json      |   8 ++
 .../main/resources/META-INF/cxs/events/form.json   |   8 ++
 .../main/resources/META-INF/cxs/events/goal.json   |   4 +
 .../resources/META-INF/cxs/events/identify.json    |   4 +
 .../META-INF/cxs/events/incrementInterest.json     |   4 +
 .../main/resources/META-INF/cxs/events/login.json  |   8 ++
 .../META-INF/cxs/events/modifyConsent.json         |   8 ++
 .../META-INF/cxs/events/profileDeleted.json        |   4 +
 .../META-INF/cxs/events/profileUpdated.json        |   4 +
 .../resources/META-INF/cxs/events/ruleFired.json   |   4 +
 .../main/resources/META-INF/cxs/events/search.json |   4 +
 .../META-INF/cxs/events/sessionCreated.json        |   4 +
 .../META-INF/cxs/events/sessionReassigned.json     |   4 +
 .../META-INF/cxs/events/updateProperties.json      |   4 +
 .../main/resources/META-INF/cxs/events/view.json   |   8 ++
 39 files changed, 523 insertions(+), 228 deletions(-)

diff --git a/api/pom.xml b/api/pom.xml
index 7bc17fa..75da098 100644
--- a/api/pom.xml
+++ b/api/pom.xml
@@ -49,6 +49,14 @@
             <groupId>org.hibernate</groupId>
             <artifactId>hibernate-validator</artifactId>
         </dependency>
+        <dependency>
+            <groupId>commons-beanutils</groupId>
+            <artifactId>commons-beanutils</artifactId>
+        </dependency>
+        <dependency>
+            <groupId>commons-collections</groupId>
+            <artifactId>commons-collections</artifactId>
+        </dependency>
     </dependencies>
 
     <reporting>
diff --git a/api/src/main/java/org/apache/unomi/api/CustomItem.java 
b/api/src/main/java/org/apache/unomi/api/CustomItem.java
index df012dd..a5dceab 100644
--- a/api/src/main/java/org/apache/unomi/api/CustomItem.java
+++ b/api/src/main/java/org/apache/unomi/api/CustomItem.java
@@ -30,6 +30,7 @@ public class CustomItem extends Item {
      */
     public static final String ITEM_TYPE = "custom";
     private static final long serialVersionUID = -7178914125308851922L;
+    private String customItemType;
     private Map<String,Object> properties = new HashMap<String,Object>();
 
     /**
@@ -66,4 +67,12 @@ public class CustomItem extends Item {
     public void setProperties(Map<String, Object> properties) {
         this.properties = properties;
     }
+
+    public String getCustomItemType() {
+        return customItemType;
+    }
+
+    public void setCustomItemType(String customItemType) {
+        this.customItemType = customItemType;
+    }
 }
diff --git a/api/src/main/java/org/apache/unomi/api/MetadataItem.java 
b/api/src/main/java/org/apache/unomi/api/MetadataItem.java
index fb8b521..78a4198 100644
--- a/api/src/main/java/org/apache/unomi/api/MetadataItem.java
+++ b/api/src/main/java/org/apache/unomi/api/MetadataItem.java
@@ -46,7 +46,9 @@ public abstract class MetadataItem extends Item {
     }
 
     public void setMetadata(Metadata metadata) {
-        this.itemId = metadata.getId();
+        if (metadata != null) {
+            this.itemId = metadata.getId();
+        }
         this.metadata = metadata;
     }
 
diff --git 
a/services/src/main/java/org/apache/unomi/services/impl/ParserHelper.java 
b/api/src/main/java/org/apache/unomi/api/utils/ParserHelper.java
similarity index 61%
rename from 
services/src/main/java/org/apache/unomi/services/impl/ParserHelper.java
rename to api/src/main/java/org/apache/unomi/api/utils/ParserHelper.java
index 0b90ec5..6396e19 100644
--- a/services/src/main/java/org/apache/unomi/services/impl/ParserHelper.java
+++ b/api/src/main/java/org/apache/unomi/api/utils/ParserHelper.java
@@ -15,8 +15,11 @@
  * limitations under the License.
  */
 
-package org.apache.unomi.services.impl;
+package org.apache.unomi.api.utils;
 
+import org.apache.commons.beanutils.PropertyUtils;
+import org.apache.commons.lang3.StringUtils;
+import org.apache.unomi.api.Event;
 import org.apache.unomi.api.PropertyType;
 import org.apache.unomi.api.ValueType;
 import org.apache.unomi.api.actions.Action;
@@ -28,6 +31,7 @@ import org.apache.unomi.api.services.DefinitionsService;
 import org.slf4j.Logger;
 import org.slf4j.LoggerFactory;
 
+import java.lang.reflect.InvocationTargetException;
 import java.util.*;
 
 /**
@@ -40,6 +44,29 @@ public class ParserHelper {
     private static final Set<String> unresolvedActionTypes = new HashSet<>();
     private static final Set<String> unresolvedConditionTypes = new 
HashSet<>();
 
+    private static final String VALUE_NAME_SEPARATOR = "::";
+    private static final String PLACEHOLDER_PREFIX = "${";
+    private static final String PLACEHOLDER_SUFFIX = "}";
+
+    public interface ConditionVisitor {
+        void visit(Condition condition);
+        void postVisit(Condition condition);
+    }
+
+    public interface ValueExtractor {
+        Object extract(String valueAsString, Event event) throws 
IllegalAccessException, NoSuchMethodException, InvocationTargetException;
+    }
+
+    public static final Map<String,ValueExtractor> DEFAULT_VALUE_EXTRACTORS = 
new HashMap<>();
+    static {
+        DEFAULT_VALUE_EXTRACTORS.put("profileProperty", (valueAsString, event) 
-> PropertyUtils.getProperty(event.getProfile(), "properties." + 
valueAsString));
+        DEFAULT_VALUE_EXTRACTORS.put("simpleProfileProperty", (valueAsString, 
event) -> event.getProfile().getProperty(valueAsString));
+        DEFAULT_VALUE_EXTRACTORS.put("sessionProperty", (valueAsString, event) 
-> PropertyUtils.getProperty(event.getSession(), "properties." + 
valueAsString));
+        DEFAULT_VALUE_EXTRACTORS.put("simpleSessionProperty", (valueAsString, 
event) -> event.getSession().getProperty(valueAsString));
+        DEFAULT_VALUE_EXTRACTORS.put("eventProperty", (valueAsString, event) 
-> PropertyUtils.getProperty(event, valueAsString));
+        DEFAULT_VALUE_EXTRACTORS.put("simpleEventProperty", (valueAsString, 
event) -> event.getProperty(valueAsString));
+    }
+
     public static boolean resolveConditionType(final DefinitionsService 
definitionsService, Condition rootCondition, String contextObjectName) {
         if (rootCondition == null) {
             logger.warn("Couldn't resolve null condition for {}", 
contextObjectName);
@@ -86,7 +113,7 @@ public class ParserHelper {
         return result;
     }
 
-    private static void visitConditions(Condition rootCondition, 
ConditionVisitor visitor) {
+    public static void visitConditions(Condition rootCondition, 
ConditionVisitor visitor) {
         visitor.visit(rootCondition);
         // recursive call for sub-conditions as parameters
         for (Object parameterValue : 
rootCondition.getParameterValues().values()) {
@@ -152,10 +179,6 @@ public class ParserHelper {
         }
     }
 
-    interface ConditionVisitor {
-        void visit(Condition condition);
-        void postVisit(Condition condition);
-    }
 
     public static Set<String> resolveConditionEventTypes(Condition 
rootCondition) {
         if (rootCondition == null) {
@@ -166,7 +189,7 @@ public class ParserHelper {
         return eventTypeConditionVisitor.getEventTypeIds();
     }
 
-    static class EventTypeConditionVisitor implements ConditionVisitor {
+    public static class EventTypeConditionVisitor implements ConditionVisitor {
 
         private Set<String> eventTypeIds = new HashSet<>();
         private Stack<String> conditionTypeStack = new Stack<>();
@@ -199,4 +222,79 @@ public class ParserHelper {
             return eventTypeIds;
         }
     }
+
+    @SuppressWarnings("unchecked")
+    public static Map<String, Object> parseMap(Event event, Map<String, 
Object> map, Map<String, ValueExtractor> valueExtractors) {
+        Map<String, Object> values = new HashMap<>();
+        for (Map.Entry<String, Object> entry : map.entrySet()) {
+            Object value = entry.getValue();
+            if (value instanceof String) {
+                String s = (String) value;
+                try {
+                    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, 
valueExtractors);
+                            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, valueExtractors);
+                        }
+                    }
+                } catch (UnsupportedOperationException e) {
+                    throw e;
+                } catch (Exception e) {
+                    throw new UnsupportedOperationException(e);
+                }
+            } else if (value instanceof Map) {
+                value = parseMap(event, (Map<String, Object>) value, 
valueExtractors);
+            }
+            values.put(entry.getKey(), value);
+        }
+        return values;
+    }
+
+    public static Object extractValue(String s, Event event, Map<String, 
ValueExtractor> valueExtractors) 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")
+    public static boolean hasContextualParameter(Map<String, Object> values, 
Map<String, ValueExtractor> valueExtractors) {
+        for (Map.Entry<String, Object> entry : values.entrySet()) {
+            Object value = entry.getValue();
+            if (value instanceof String) {
+                String s = (String) value;
+                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) {
+                if (hasContextualParameter((Map<String, Object>) value, 
valueExtractors)) {
+                    return true;
+                }
+            }
+        }
+        return false;
+    }
+
 }
diff --git a/itests/pom.xml b/itests/pom.xml
index e3bec40..50c1ccf 100644
--- a/itests/pom.xml
+++ b/itests/pom.xml
@@ -178,7 +178,7 @@
                             <autoCreateIndex>true</autoCreateIndex>
                             <timeout>120</timeout>
                             <environmentVariables>
-                                <ES_JAVA_OPTS>-Xms2g -Xmx2g</ES_JAVA_OPTS>
+                                <ES_JAVA_OPTS>-Xms4g -Xmx4g</ES_JAVA_OPTS>
                             </environmentVariables>
                             <instanceSettings>
                                 <properties>
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 8d86dcb..d7a2387 100644
--- a/itests/src/test/java/org/apache/unomi/itests/BasicIT.java
+++ b/itests/src/test/java/org/apache/unomi/itests/BasicIT.java
@@ -152,12 +152,15 @@ public class BasicIT extends BaseIT {
         ConditionType conditionType = 
CustomObjectMapper.getObjectMapper().readValue(
                 new 
File("data/tmp/testLoginEventCondition.json").toURI().toURL(), 
ConditionType.class);
         definitionsService.setConditionType(conditionType);
+
+        refreshPersistence();
         Thread.sleep(2000);
         // Add login rule
         Rule rule = CustomObjectMapper.getObjectMapper().readValue(new 
File("data/tmp/testLogin.json").toURI().toURL(),
                 Rule.class);
         rulesService.setRule(rule);
         Thread.sleep(2000);
+        refreshPersistence();
 
         CustomItem sourceSite = new CustomItem(ITEM_ID_SITE, ITEM_TYPE_SITE);
         sourceSite.setScope(TEST_SCOPE);
@@ -260,6 +263,8 @@ public class BasicIT extends BaseIT {
         
checkVisitor2ResponseProperties(requestResponsePageView4.getContextResponse().getProfileProperties());
         Thread.sleep(1000);
 
+        refreshPersistence();
+
         // Check both visitor profile at the end by loading them directly
         Profile profileVisitor1 = profileService.load(profileIdVisitor1);
         checkVisitor1ResponseProperties(profileVisitor1.getProperties());
diff --git a/itests/src/test/resources/testLogin.json 
b/itests/src/test/resources/testLogin.json
index 556b32c..e6af848 100644
--- a/itests/src/test/resources/testLogin.json
+++ b/itests/src/test/resources/testLogin.json
@@ -19,15 +19,13 @@
   },
   "actions": [
     {
+      "type": "mergeProfilesOnPropertyAction",
       "parameterValues": {
         "mergeProfilePropertyValue": "eventProperty::target.properties(email)",
         "mergeProfilePropertyName": "mergeIdentifier"
-      },
-      "type": "mergeProfilesOnPropertyAction"
+      }
     },
     {
-      "parameterValues": {
-      },
       "type": "copyPropertiesAction",
       "parameterValues": {
         "singleValueStrategy": "alwaysSet"
diff --git 
a/persistence-elasticsearch/core/src/main/java/org/apache/unomi/persistence/elasticsearch/ElasticSearchPersistenceServiceImpl.java
 
b/persistence-elasticsearch/core/src/main/java/org/apache/unomi/persistence/elasticsearch/ElasticSearchPersistenceServiceImpl.java
index 4b5d6f1..211ef68 100644
--- 
a/persistence-elasticsearch/core/src/main/java/org/apache/unomi/persistence/elasticsearch/ElasticSearchPersistenceServiceImpl.java
+++ 
b/persistence-elasticsearch/core/src/main/java/org/apache/unomi/persistence/elasticsearch/ElasticSearchPersistenceServiceImpl.java
@@ -28,10 +28,7 @@ import org.apache.http.client.CredentialsProvider;
 import org.apache.http.conn.ssl.NoopHostnameVerifier;
 import org.apache.http.impl.client.BasicCredentialsProvider;
 import org.apache.lucene.search.TotalHits;
-import org.apache.unomi.api.Item;
-import org.apache.unomi.api.PartialList;
-import org.apache.unomi.api.PropertyType;
-import org.apache.unomi.api.TimestampedItem;
+import org.apache.unomi.api.*;
 import org.apache.unomi.api.conditions.Condition;
 import org.apache.unomi.api.query.DateRange;
 import org.apache.unomi.api.query.IpRange;
@@ -778,22 +775,48 @@ public class ElasticSearchPersistenceServiceImpl 
implements PersistenceService,
 
     @Override
     public <T extends Item> T load(final String itemId, final Date dateHint, 
final Class<T> clazz) {
+        return load(itemId, dateHint, clazz, null);
+    }
+
+    @Override
+    public CustomItem loadCustomItem(final String itemId, final Date dateHint, 
String customItemType) {
+        return load(itemId, dateHint, CustomItem.class, customItemType);
+    }
+
+    private <T extends Item> T load(final String itemId, final Date dateHint, 
final Class<T> clazz, final String customItemType) {
         return new InClassLoaderExecute<T>(metricsService, 
this.getClass().getName() + ".loadItem", this.bundleContext, 
this.fatalIllegalStateErrors, throwExceptions) {
             protected T execute(Object... args) throws Exception {
                 try {
                     String itemType = Item.getItemType(clazz);
-                    T itemFromCache = getFromCache(itemId, clazz);
-                    if (itemFromCache != null) {
-                        return itemFromCache;
+                    String className = clazz.getName();
+                    if (customItemType == null) {
+                        T itemFromCache = getFromCache(itemId, 
clazz.getName());
+                        if (itemFromCache != null) {
+                            return itemFromCache;
+                        }
+                    } else {
+                        T itemFromCache = getFromCache(itemId, 
CustomItem.class.getName() + "." + customItemType);
+                        if (itemFromCache != null) {
+                            return itemFromCache;
+                        }
+                        className = CustomItem.class.getName() + "." + 
customItemType;
+                        itemType = customItemType;
                     }
 
                     if (itemsMonthlyIndexed.contains(itemType) && dateHint == 
null) {
                         return new MetricAdapter<T>(metricsService, 
".loadItemWithQuery") {
                             @Override
                             public T execute(Object... args) throws Exception {
-                                PartialList<T> r = 
query(QueryBuilders.idsQuery().addIds(itemId), null, clazz, 0, 1, null, null);
-                                if (r.size() > 0) {
-                                    return r.get(0);
+                                if (customItemType == null) {
+                                    PartialList<T> r = 
query(QueryBuilders.idsQuery().addIds(itemId), null, clazz, 0, 1, null, null);
+                                    if (r.size() > 0) {
+                                        return r.get(0);
+                                    }
+                                } else {
+                                    PartialList<CustomItem> r = 
query(QueryBuilders.idsQuery().addIds(itemId), null, customItemType, 0, 1, 
null, null);
+                                    if (r.size() > 0) {
+                                        return (T) r.get(0);
+                                    }
                                 }
                                 return null;
                             }
@@ -805,7 +828,7 @@ public class ElasticSearchPersistenceServiceImpl implements 
PersistenceService,
                             String sourceAsString = 
response.getSourceAsString();
                             final T value = 
ESCustomObjectMapper.getObjectMapper().readValue(sourceAsString, clazz);
                             setMetadata(value, response.getId(), 
response.getVersion(), response.getSeqNo(), response.getPrimaryTerm());
-                            putInCache(itemId, value);
+                            putInCache(itemId, value, className);
                             return value;
                         } else {
                             return null;
@@ -816,12 +839,12 @@ public class ElasticSearchPersistenceServiceImpl 
implements PersistenceService,
                         // this can happen if we are just testing the 
existence of the item, it is not always an error.
                         return null;
                     }
-                    throw new Exception("Error loading itemType=" + 
clazz.getName() + " itemId=" + itemId, ese);
+                    throw new Exception("Error loading itemType=" + 
clazz.getName() + " customItemType=" + customItemType + " itemId=" + itemId, 
ese);
                 } catch (IndexNotFoundException e) {
                     // this can happen if we are just testing the existence of 
the item, it is not always an error.
                     return null;
                 } catch (Exception ex) {
-                    throw new Exception("Error loading itemType=" + 
clazz.getName() + " itemId=" + itemId, ex);
+                    throw new Exception("Error loading itemType=" + 
clazz.getName() + " customItemType=" + customItemType+ " itemId=" + itemId, ex);
                 }
             }
         }.catchingExecuteInClassLoader(true);
@@ -860,8 +883,13 @@ public class ElasticSearchPersistenceServiceImpl 
implements PersistenceService,
                 try {
                     String source = 
ESCustomObjectMapper.getObjectMapper().writeValueAsString(item);
                     String itemType = item.getItemType();
+                    String className = item.getClass().getName();
+                    if (item instanceof CustomItem) {
+                        itemType = ((CustomItem) item).getCustomItemType();
+                        className = CustomItem.class.getName() + "." + 
itemType;
+                    }
                     String itemId = item.getItemId();
-                    putInCache(itemId, item);
+                    putInCache(itemId, item, className);
                     String index = getIndex(itemType, 
itemsMonthlyIndexed.contains(itemType) ? ((TimestampedItem) 
item).getTimeStamp() : null);
                     IndexRequest indexRequest = new IndexRequest(index);
                     indexRequest.id(itemId);
@@ -886,7 +914,7 @@ public class ElasticSearchPersistenceServiceImpl implements 
PersistenceService,
 
                     try {
                         if (bulkProcessor == null || !useBatching) {
-                            
indexRequest.setRefreshPolicy(getRefreshPolicy(item.getItemType()));
+                            
indexRequest.setRefreshPolicy(getRefreshPolicy(itemType));
                             IndexResponse response = 
client.index(indexRequest, RequestOptions.DEFAULT);
                             setMetadata(item, response.getId(), 
response.getVersion(), response.getSeqNo(), response.getPrimaryTerm());
                         } else {
@@ -1150,10 +1178,22 @@ public class ElasticSearchPersistenceServiceImpl 
implements PersistenceService,
 
     @Override
     public <T extends Item> boolean remove(final String itemId, final Class<T> 
clazz) {
+        return remove(itemId, clazz, null);
+    }
+
+    @Override
+    public boolean removeCustomItem(final String itemId, final String 
customItemType) {
+        return remove(itemId, CustomItem.class, customItemType);
+    }
+
+    private <T extends Item> boolean remove(final String itemId, final 
Class<T> clazz, String customItemType) {
         Boolean result = new InClassLoaderExecute<Boolean>(metricsService, 
this.getClass().getName() + ".removeItem", this.bundleContext, 
this.fatalIllegalStateErrors, throwExceptions) {
             protected Boolean execute(Object... args) throws Exception {
                 try {
                     String itemType = Item.getItemType(clazz);
+                    if (customItemType != null) {
+                        itemType = customItemType;
+                    }
 
                     DeleteRequest deleteRequest = new 
DeleteRequest(getIndexNameForQuery(itemType), itemId);
                     client.delete(deleteRequest, RequestOptions.DEFAULT);
@@ -1194,7 +1234,7 @@ public class ElasticSearchPersistenceServiceImpl 
implements PersistenceService,
 
                         for (SearchHit hit : response.getHits().getHits()) {
                             // add hit to bulk delete
-                            deleteFromCache(hit.getId(), clazz);
+                            deleteFromCache(hit.getId(), clazz.getName());
                             
deleteByScopeBulkRequest.add(Requests.deleteRequest(hit.getIndex()).type(hit.getType()).id(hit.getId()));
                         }
 
@@ -1370,7 +1410,9 @@ public class ElasticSearchPersistenceServiceImpl 
implements PersistenceService,
                 "    }\n" +
                 "}\n", XContentType.JSON);
 
-        createIndexRequest.mapping(mappingSource, XContentType.JSON);
+        if (mappingSource != null) {
+            createIndexRequest.mapping(mappingSource, XContentType.JSON);
+        }
         CreateIndexResponse createIndexResponse = 
client.indices().create(createIndexRequest, RequestOptions.DEFAULT);
         logger.info("Index created: [{}], acknowledge: [{}], shards 
acknowledge: [{}]", createIndexResponse.index(),
                 createIndexResponse.isAcknowledged(), 
createIndexResponse.isShardsAcknowledged());
@@ -1697,6 +1739,11 @@ public class ElasticSearchPersistenceServiceImpl 
implements PersistenceService,
     }
 
     @Override
+    public PartialList<CustomItem> queryCustomItem(final Condition query, 
String sortBy, final String customItemType, final int offset, final int size, 
final String scrollTimeValidity) {
+        return query(conditionESQueryBuilderDispatcher.getQueryBuilder(query), 
sortBy, customItemType, offset, size, null, scrollTimeValidity);
+    }
+
+    @Override
     public <T extends Item> PartialList<T> queryFullText(final String 
fulltext, final Condition query, String sortBy, final Class<T> clazz, final int 
offset, final int size) {
         return 
query(QueryBuilders.boolQuery().must(QueryBuilders.queryStringQuery(fulltext)).must(conditionESQueryBuilderDispatcher.getQueryBuilder(query)),
 sortBy, clazz, offset, size, null, null);
     }
@@ -1768,6 +1815,14 @@ public class ElasticSearchPersistenceServiceImpl 
implements PersistenceService,
     }
 
     private <T extends Item> PartialList<T> query(final QueryBuilder query, 
final String sortBy, final Class<T> clazz, final int offset, final int size, 
final String[] routing, final String scrollTimeValidity) {
+        return query(query, sortBy, clazz, null, offset, size, routing, 
scrollTimeValidity);
+    }
+
+    private PartialList<CustomItem> query(final QueryBuilder query, final 
String sortBy, final String customItemType, final int offset, final int size, 
final String[] routing, final String scrollTimeValidity) {
+        return query(query, sortBy, CustomItem.class, customItemType, offset, 
size, routing, scrollTimeValidity);
+    }
+
+    private <T extends Item> PartialList<T> query(final QueryBuilder query, 
final String sortBy, final Class<T> clazz, final String customItemType, final 
int offset, final int size, final String[] routing, final String 
scrollTimeValidity) {
         return new InClassLoaderExecute<PartialList<T>>(metricsService, 
this.getClass().getName() + ".query", this.bundleContext, 
this.fatalIllegalStateErrors, throwExceptions) {
 
             @Override
@@ -1778,6 +1833,9 @@ public class ElasticSearchPersistenceServiceImpl 
implements PersistenceService,
                 PartialList.Relation totalHitsRelation = 
PartialList.Relation.EQUAL;
                 try {
                     String itemType = Item.getItemType(clazz);
+                    if (customItemType != null) {
+                        itemType = customItemType;
+                    }
                     TimeValue keepAlive = TimeValue.timeValueHours(1);
                     SearchRequest searchRequest = new 
SearchRequest(getIndexNameForQuery(itemType));
                     SearchSourceBuilder searchSourceBuilder = new 
SearchSourceBuilder()
@@ -1933,6 +1991,47 @@ public class ElasticSearchPersistenceServiceImpl 
implements PersistenceService,
         }.catchingExecuteInClassLoader(true);
     }
 
+    @Override
+    public PartialList<CustomItem> continueCustomItemScrollQuery(final String 
customItemType, final String scrollIdentifier, final String scrollTimeValidity) 
{
+        return new 
InClassLoaderExecute<PartialList<CustomItem>>(metricsService, 
this.getClass().getName() + ".continueScrollQuery", this.bundleContext, 
this.fatalIllegalStateErrors, throwExceptions) {
+
+            @Override
+            protected PartialList<CustomItem> execute(Object... args) throws 
Exception {
+                List<CustomItem> results = new ArrayList<CustomItem>();
+                long totalHits = 0;
+                try {
+                    TimeValue keepAlive = 
TimeValue.parseTimeValue(scrollTimeValidity, TimeValue.timeValueMinutes(10), 
"scrollTimeValidity");
+
+                    SearchScrollRequest searchScrollRequest = new 
SearchScrollRequest(scrollIdentifier);
+                    searchScrollRequest.scroll(keepAlive);
+                    SearchResponse response = 
client.scroll(searchScrollRequest, RequestOptions.DEFAULT);
+
+                    if (response.getHits().getHits().length == 0) {
+                        ClearScrollRequest clearScrollRequest = new 
ClearScrollRequest();
+                        clearScrollRequest.addScrollId(response.getScrollId());
+                        client.clearScroll(clearScrollRequest, 
RequestOptions.DEFAULT);
+                    } else {
+                        for (SearchHit searchHit : 
response.getHits().getHits()) {
+                            // add hit to results
+                            String sourceAsString = 
searchHit.getSourceAsString();
+                            final CustomItem value = 
ESCustomObjectMapper.getObjectMapper().readValue(sourceAsString, 
CustomItem.class);
+                            setMetadata(value, searchHit.getId(), 
searchHit.getVersion(), searchHit.getSeqNo(), searchHit.getPrimaryTerm());
+                            results.add(value);
+                        }
+                    }
+                    PartialList<CustomItem> result = new 
PartialList<CustomItem>(results, 0, response.getHits().getHits().length, 
response.getHits().getTotalHits().value, 
getTotalHitsRelation(response.getHits().getTotalHits()));
+                    if (scrollIdentifier != null) {
+                        result.setScrollIdentifier(scrollIdentifier);
+                        result.setScrollTimeValidity(scrollTimeValidity);
+                    }
+                    return result;
+                } catch (Exception t) {
+                    throw new Exception("Error continuing scrolling query for 
itemType=" + customItemType + " scrollIdentifier=" + scrollIdentifier + " 
scrollTimeValidity=" + scrollTimeValidity, t);
+                }
+            }
+        }.catchingExecuteInClassLoader(true);
+    }
+
     /**
      * @deprecated As of version 1.3.0-incubating, use {@link 
#aggregateWithOptimizedQuery(Condition, BaseAggregate, String)} instead
      */
@@ -2405,8 +2504,7 @@ public class ElasticSearchPersistenceServiceImpl 
implements PersistenceService,
         return false;
     }
 
-    private <T extends Item> T getFromCache(String itemId, Class<T> clazz) {
-        String className = clazz.getName();
+    private <T extends Item> T getFromCache(String itemId, String className) {
         if (!isCacheActiveForClass(className)) {
             return null;
         }
@@ -2414,8 +2512,7 @@ public class ElasticSearchPersistenceServiceImpl 
implements PersistenceService,
         return itemCache.get(itemId);
     }
 
-    private <T extends Item> T putInCache(String itemId, T item) {
-        String className = item.getClass().getName();
+    private <T extends Item> T putInCache(String itemId, T item, String 
className) {
         if (!isCacheActiveForClass(className)) {
             return null;
         }
@@ -2423,8 +2520,7 @@ public class ElasticSearchPersistenceServiceImpl 
implements PersistenceService,
         return itemCache.put(itemId, item);
     }
 
-    private <T extends Item> T deleteFromCache(String itemId, Class clazz) {
-        String className = clazz.getName();
+    private <T extends Item> T deleteFromCache(String itemId, String 
className) {
         if (!isCacheActiveForClass(className)) {
             return null;
         }
diff --git 
a/persistence-elasticsearch/core/src/main/java/org/apache/unomi/persistence/elasticsearch/conditions/ConditionContextHelper.java
 
b/persistence-elasticsearch/core/src/main/java/org/apache/unomi/persistence/elasticsearch/conditions/ConditionContextHelper.java
index 13bc667..211ed32 100644
--- 
a/persistence-elasticsearch/core/src/main/java/org/apache/unomi/persistence/elasticsearch/conditions/ConditionContextHelper.java
+++ 
b/persistence-elasticsearch/core/src/main/java/org/apache/unomi/persistence/elasticsearch/conditions/ConditionContextHelper.java
@@ -18,6 +18,7 @@
 package org.apache.unomi.persistence.elasticsearch.conditions;
 
 import com.google.common.base.Function;
+import com.google.common.collect.Collections2;
 import com.google.common.collect.Lists;
 import org.apache.commons.lang3.StringUtils;
 import org.apache.logging.log4j.core.util.IOUtils;
@@ -31,10 +32,7 @@ import org.slf4j.LoggerFactory;
 import java.io.IOException;
 import java.io.Reader;
 import java.io.StringReader;
-import java.util.ArrayList;
-import java.util.HashMap;
-import java.util.List;
-import java.util.Map;
+import java.util.*;
 
 public class ConditionContextHelper {
     private static final Logger logger = 
LoggerFactory.getLogger(ConditionContextHelper.class);
@@ -144,9 +142,9 @@ public class ConditionContextHelper {
         return null;
     }
 
-    public static <T> List<T> foldToASCII(List<T> s) {
+    public static <T> Collection<T> foldToASCII(Collection<T> s) {
         if (s != null) {
-            return Lists.transform(s, new Function<T, T>() {
+            return Collections2.transform(s, new Function<T, T>() {
                 @Override
                 public T apply(T o) {
                     if (o instanceof String) {
diff --git 
a/persistence-spi/src/main/java/org/apache/unomi/persistence/spi/CustomObjectMapper.java
 
b/persistence-spi/src/main/java/org/apache/unomi/persistence/spi/CustomObjectMapper.java
index 4f967c8..f5f2844 100644
--- 
a/persistence-spi/src/main/java/org/apache/unomi/persistence/spi/CustomObjectMapper.java
+++ 
b/persistence-spi/src/main/java/org/apache/unomi/persistence/spi/CustomObjectMapper.java
@@ -44,6 +44,10 @@ import java.util.TimeZone;
 public class CustomObjectMapper extends ObjectMapper {
 
     private static final long serialVersionUID = 4578277612897061535L;
+    
+    private Map<String,Class<? extends Item>> builtinItemTypeClasses = new 
HashMap<>();
+    private PropertyTypedObjectDeserializer propertyTypedObjectDeserializer;
+    private ItemDeserializer itemDeserializer;
 
     public CustomObjectMapper() {
         super();
@@ -56,45 +60,61 @@ public class CustomObjectMapper extends ObjectMapper {
                 new SimpleModule("PropertyTypedObjectDeserializerModule",
                         new Version(1, 0, 0, null, "org.apache.unomi.rest", 
"deserializer"));
 
-        PropertyTypedObjectDeserializer propertyTypedObjectDeserializer = new 
PropertyTypedObjectDeserializer(null, null);
+        propertyTypedObjectDeserializer = new 
PropertyTypedObjectDeserializer(null, null);
         propertyTypedObjectDeserializer.registerMapping("type=.*Condition", 
Condition.class);
         deserializerModule.addDeserializer(Object.class, 
propertyTypedObjectDeserializer);
 
-        ItemDeserializer itemDeserializer = new ItemDeserializer();
+        itemDeserializer = new ItemDeserializer();
         deserializerModule.addDeserializer(Item.class, itemDeserializer);
 
 
-        Map<String, Class<? extends Item>> classes = new HashMap<>();
-        classes.put(Campaign.ITEM_TYPE, Campaign.class);
-        classes.put(CampaignEvent.ITEM_TYPE, CampaignEvent.class);
-        classes.put(Event.ITEM_TYPE, Event.class);
-        classes.put(Goal.ITEM_TYPE, Goal.class);
-        classes.put(Persona.ITEM_TYPE, Persona.class);
-        classes.put(Profile.ITEM_TYPE, Profile.class);
-        classes.put(Rule.ITEM_TYPE, Rule.class);
-        classes.put(Scoring.ITEM_TYPE, Scoring.class);
-        classes.put(Segment.ITEM_TYPE, Segment.class);
-        classes.put(Session.ITEM_TYPE, Session.class);
-        classes.put(ConditionType.ITEM_TYPE, ConditionType.class);
-        classes.put(ActionType.ITEM_TYPE, ActionType.class);
-        classes.put(Topic.ITEM_TYPE, Topic.class);
-        classes.put(SourceItem.ITEM_TYPE, SourceItem.class);
-        classes.put(ProfileAlias.ITEM_TYPE, ProfileAlias.class);
-        for (Map.Entry<String, Class<? extends Item>> entry : 
classes.entrySet()) {
+        builtinItemTypeClasses = new HashMap<>();
+        builtinItemTypeClasses.put(Campaign.ITEM_TYPE, Campaign.class);
+        builtinItemTypeClasses.put(CampaignEvent.ITEM_TYPE, 
CampaignEvent.class);
+        builtinItemTypeClasses.put(Event.ITEM_TYPE, Event.class);
+        builtinItemTypeClasses.put(Goal.ITEM_TYPE, Goal.class);
+        builtinItemTypeClasses.put(Persona.ITEM_TYPE, Persona.class);
+        builtinItemTypeClasses.put(Profile.ITEM_TYPE, Profile.class);
+        builtinItemTypeClasses.put(Rule.ITEM_TYPE, Rule.class);
+        builtinItemTypeClasses.put(Scoring.ITEM_TYPE, Scoring.class);
+        builtinItemTypeClasses.put(Segment.ITEM_TYPE, Segment.class);
+        builtinItemTypeClasses.put(Session.ITEM_TYPE, Session.class);
+        builtinItemTypeClasses.put(ConditionType.ITEM_TYPE, 
ConditionType.class);
+        builtinItemTypeClasses.put(ActionType.ITEM_TYPE, ActionType.class);
+        builtinItemTypeClasses.put(Topic.ITEM_TYPE, Topic.class);
+        builtinItemTypeClasses.put(SourceItem.ITEM_TYPE, SourceItem.class);
+        builtinItemTypeClasses.put(ProfileAlias.ITEM_TYPE, ProfileAlias.class);
+        for (Map.Entry<String, Class<? extends Item>> entry : 
builtinItemTypeClasses.entrySet()) {
             propertyTypedObjectDeserializer.registerMapping("itemType=" + 
entry.getKey(), entry.getValue());
             itemDeserializer.registerMapping(entry.getKey(), entry.getValue());
         }
         propertyTypedObjectDeserializer.registerMapping("itemType=.*", 
CustomItem.class);
 
-
         super.registerModule(deserializerModule);
     }
 
+    public void registerBuiltInItemTypeClass(String itemType, Class clazz) {
+        propertyTypedObjectDeserializer.registerMapping("itemType=" + 
itemType, clazz);
+        itemDeserializer.registerMapping(itemType, clazz);
+    }
+
+    public void unregisterBuiltInItemTypeClass(String itemType) {
+        propertyTypedObjectDeserializer.unregisterMapping("itemType=" + 
itemType);
+        itemDeserializer.unregisterMapping(itemType);
+    }
+
     public static ObjectMapper getObjectMapper() {
         return Holder.INSTANCE;
     }
 
+    public static CustomObjectMapper getCustomInstance() { return 
Holder.INSTANCE; }
+
+    public Class<? extends Item> getBuiltinItemTypeClass(String itemType) {
+        return builtinItemTypeClasses.get(itemType);
+    }
+
     private static class Holder {
         static final CustomObjectMapper INSTANCE = new CustomObjectMapper();
     }
+
 }
diff --git 
a/persistence-spi/src/main/java/org/apache/unomi/persistence/spi/ItemDeserializer.java
 
b/persistence-spi/src/main/java/org/apache/unomi/persistence/spi/ItemDeserializer.java
index 9080907..32cc135 100644
--- 
a/persistence-spi/src/main/java/org/apache/unomi/persistence/spi/ItemDeserializer.java
+++ 
b/persistence-spi/src/main/java/org/apache/unomi/persistence/spi/ItemDeserializer.java
@@ -42,6 +42,10 @@ public class ItemDeserializer extends StdDeserializer<Item> {
         classes.put(type, clazz);
     }
 
+    public void unregisterMapping(String type) {
+        classes.remove(type);
+    }
+
     @Override
     public Item deserialize(JsonParser jp, DeserializationContext ctxt) throws 
IOException {
         ObjectCodec codec = jp.getCodec();
@@ -55,6 +59,9 @@ public class ItemDeserializer extends StdDeserializer<Item> {
         }
         Item item = codec.treeToValue(treeNode, objectClass);
         item.setItemId(treeNode.get("itemId").asText());
+        if (item instanceof CustomItem) {
+            ((CustomItem) item).setCustomItemType(type);
+        }
         return item;
     }
 }
diff --git 
a/persistence-spi/src/main/java/org/apache/unomi/persistence/spi/PersistenceService.java
 
b/persistence-spi/src/main/java/org/apache/unomi/persistence/spi/PersistenceService.java
index 4eaa1dd..625a594 100644
--- 
a/persistence-spi/src/main/java/org/apache/unomi/persistence/spi/PersistenceService.java
+++ 
b/persistence-spi/src/main/java/org/apache/unomi/persistence/spi/PersistenceService.java
@@ -17,6 +17,7 @@
 
 package org.apache.unomi.persistence.spi;
 
+import org.apache.unomi.api.CustomItem;
 import org.apache.unomi.api.Item;
 import org.apache.unomi.api.PartialList;
 import org.apache.unomi.api.PropertyType;
@@ -236,6 +237,15 @@ public interface PersistenceService {
     <T extends Item> T load(String itemId, Date dateHint, Class<T> clazz);
 
     /**
+     * Load a custom item type identified by an identifier, an optional date 
hint and the identifier of the custom item type
+     * @param itemId the identifier of the custom type we want to retrieve
+     * @param dateHint an optional Date object if the custom item types are 
stored by date
+     * @param customItemType an identifier of the custom item type to load
+     * @return the CustomItem instance with the specified identifier and the 
custom item type if it exists, {@code null} otherwise
+     */
+    CustomItem loadCustomItem(String itemId, Date dateHint, String 
customItemType);
+
+    /**
      * Deletes the item identified with the specified identifier and with the 
specified Item subclass if it exists.
      *
      * @param <T>    the type of the Item subclass we want to delete
@@ -246,6 +256,14 @@ public interface PersistenceService {
     <T extends Item> boolean remove(String itemId, Class<T> clazz);
 
     /**
+     * Remove a custom item identified by the custom item identifier and the 
custom item type identifier
+     * @param itemId the identifier of the custom item to be removed
+     * @param customItemType the name of the custom item type
+     * @return {@code true} if the deletion was successful, {@code false} 
otherwise
+     */
+    boolean removeCustomItem(String itemId, String customItemType);
+
+    /**
      * Deletes items with the specified Item subclass matching the specified 
{@link Condition}.
      *
      * @param <T>   the type of the Item subclass we want to delete
@@ -476,6 +494,39 @@ public interface PersistenceService {
     <T extends Item> PartialList<T> continueScrollQuery(Class<T> clazz, String 
scrollIdentifier, String scrollTimeValidity);
 
     /**
+     * Retrieves a list of items satisfying the specified {@link Condition}, 
ordered according to the specified
+     * {@code sortBy} String and paged: only {@code size} of them are 
retrieved, starting with the
+     * {@code offset}-th one. If a scroll identifier and time validity are 
specified, they will be used to perform a
+     * scrolling query, meaning that only partial results will be returned, 
but the scrolling can be continued.
+     *
+     * @param query the {@link Condition} the items must satisfy to be 
retrieved
+     * @param sortBy an optional ({@code null} if no sorting is required) 
String of comma ({@code ,}) separated property names on which ordering should 
be performed, ordering
+     *               elements according to the property order in the
+     *               String, considering each in turn and moving on to the 
next one in case of equality of all preceding ones. Each property name is 
optionally followed by
+     *               a column ({@code :}) and an order specifier: {@code asc} 
or {@code desc}.
+     * @param customItemType the identifier of the custom item type we want to 
query
+     * @param offset zero or a positive integer specifying the position of the 
first item in the total ordered collection of matching items
+     * @param size   a positive integer specifying how many matching items 
should be retrieved or {@code -1} if all of them should be retrieved. In the 
case of a scroll query
+     *               this will be used as the scrolling window size.
+     * @param scrollTimeValidity the time the scrolling query should stay 
valid. This must contain a time unit value such as the ones supported by 
ElasticSearch, such as
+     *                           the ones declared here : 
https://www.elastic.co/guide/en/elasticsearch/reference/current/common-options.html#time-units
+     * @return a {@link PartialList} of items matching the specified criteria, 
with an scroll identifier and the scroll validity used if a scroll query was 
requested.
+     */
+    PartialList<CustomItem> queryCustomItem(Condition query, String sortBy, 
String customItemType, int offset, int size, String scrollTimeValidity);
+
+    /**
+     * Continues the execution of a scroll query, to retrieve the next 
results. If there are no more results the scroll query is also cleared.
+     *
+     * @param customItemType the identifier of the custom item type we want to 
continue querying
+     * @param scrollIdentifier a scroll identifier obtained by the execution 
of a first query and returned in the {@link PartialList} object
+     * @param scrollTimeValidity a scroll time validity value for the scroll 
query to stay valid. This must contain a time unit value such as the ones 
supported by ElasticSearch, such as
+     *                           the ones declared here : 
https://www.elastic.co/guide/en/elasticsearch/reference/current/common-options.html#time-units
+     * @return a {@link PartialList} of items matching the specified criteria, 
with an scroll identifier and the scroll validity used if a scroll query was 
requested. Note that if
+     * there are no more results the list will be empty but not null.
+     */
+    PartialList<CustomItem> continueCustomItemScrollQuery(String 
customItemType, String scrollIdentifier, String scrollTimeValidity);
+
+    /**
      * Retrieves the same items as {@code query(query, sortBy, clazz, 0, -1)} 
with the added constraints that the matching elements must also have at least a 
field matching the
      * specified full text query.
      *
diff --git 
a/persistence-spi/src/main/java/org/apache/unomi/persistence/spi/PropertyTypedObjectDeserializer.java
 
b/persistence-spi/src/main/java/org/apache/unomi/persistence/spi/PropertyTypedObjectDeserializer.java
index 23a556e..83f676b 100644
--- 
a/persistence-spi/src/main/java/org/apache/unomi/persistence/spi/PropertyTypedObjectDeserializer.java
+++ 
b/persistence-spi/src/main/java/org/apache/unomi/persistence/spi/PropertyTypedObjectDeserializer.java
@@ -75,6 +75,17 @@ public class PropertyTypedObjectDeserializer extends 
UntypedObjectDeserializer {
         fieldValuesToMatch.put(fieldParts[0], valuesToMatch);
     }
 
+    public void unregisterMapping(String matchExpression) {
+        registry.remove(matchExpression);
+        String[] fieldParts = matchExpression.split("=");
+        Set<String> valuesToMatch = fieldValuesToMatch.get(fieldParts[0]);
+        if (valuesToMatch == null) {
+            valuesToMatch = new LinkedHashSet<String>();
+        }
+        valuesToMatch.remove(fieldParts[1]);
+        fieldValuesToMatch.remove(fieldParts[0], valuesToMatch);
+    }
+
     @Override
     public Object deserialize(
             JsonParser jp, DeserializationContext ctxt)
diff --git 
a/plugins/baseplugin/src/main/java/org/apache/unomi/plugins/baseplugin/conditions/PropertyConditionESQueryBuilder.java
 
b/plugins/baseplugin/src/main/java/org/apache/unomi/plugins/baseplugin/conditions/PropertyConditionESQueryBuilder.java
index 1613081..4f2612b 100644
--- 
a/plugins/baseplugin/src/main/java/org/apache/unomi/plugins/baseplugin/conditions/PropertyConditionESQueryBuilder.java
+++ 
b/plugins/baseplugin/src/main/java/org/apache/unomi/plugins/baseplugin/conditions/PropertyConditionESQueryBuilder.java
@@ -31,10 +31,7 @@ import org.joda.time.DateTime;
 import org.joda.time.format.DateTimeFormatter;
 import org.joda.time.format.ISODateTimeFormat;
 
-import java.util.ArrayList;
-import java.util.Date;
-import java.util.List;
-import java.util.Map;
+import java.util.*;
 
 public class PropertyConditionESQueryBuilder implements 
ConditionESQueryBuilder {
 
@@ -59,15 +56,15 @@ public class PropertyConditionESQueryBuilder implements 
ConditionESQueryBuilder
         Object expectedValueDate = 
convertDateToISO(condition.getParameter("propertyValueDate"));
         Object expectedValueDateExpr = 
condition.getParameter("propertyValueDateExpr");
 
-        List<?> expectedValues = ConditionContextHelper.foldToASCII((List<?>) 
condition.getParameter("propertyValues"));
-        List<?> expectedValuesInteger = (List<?>) 
condition.getParameter("propertyValuesInteger");
-        List<?> expectedValuesDouble = (List<?>) 
condition.getParameter("propertyValuesDouble");
-        List<?> expectedValuesDate = convertDatesToISO((List<?>) 
condition.getParameter("propertyValuesDate"));
-        List<?> expectedValuesDateExpr = (List<?>) 
condition.getParameter("propertyValuesDateExpr");
+        Collection<?> expectedValues = 
ConditionContextHelper.foldToASCII((Collection<?>) 
condition.getParameter("propertyValues"));
+        Collection<?> expectedValuesInteger = (Collection<?>) 
condition.getParameter("propertyValuesInteger");
+        Collection<?> expectedValuesDouble = (Collection<?>) 
condition.getParameter("propertyValuesDouble");
+        Collection<?> expectedValuesDate = convertDatesToISO((Collection<?>) 
condition.getParameter("propertyValuesDate"));
+        Collection<?> expectedValuesDateExpr = (Collection<?>) 
condition.getParameter("propertyValuesDateExpr");
 
         Object value = ObjectUtils.firstNonNull(expectedValue, 
expectedValueInteger, expectedValueDouble, expectedValueDate, 
expectedValueDateExpr);
         @SuppressWarnings("unchecked")
-        List<?> values = ObjectUtils.firstNonNull(expectedValues, 
expectedValuesInteger, expectedValuesDouble, expectedValuesDate, 
expectedValuesDateExpr);
+        Collection<?> values = ObjectUtils.firstNonNull(expectedValues, 
expectedValuesInteger, expectedValuesDouble, expectedValuesDate, 
expectedValuesDateExpr);
 
         switch (comparisonOperator) {
             case "equals":
@@ -90,7 +87,8 @@ public class PropertyConditionESQueryBuilder implements 
ConditionESQueryBuilder
                 return QueryBuilders.rangeQuery(name).lte(value);
             case "between":
                 checkRequiredValuesSize(values, name, comparisonOperator, 2);
-                return 
QueryBuilders.rangeQuery(name).gte(values.get(0)).lte(values.get(1));
+                Iterator<?> iterator = values.iterator();
+                return 
QueryBuilders.rangeQuery(name).gte(iterator.next()).lte(iterator.next());
             case "exists":
                 return QueryBuilders.existsQuery(name);
             case "missing":
@@ -175,7 +173,7 @@ public class PropertyConditionESQueryBuilder implements 
ConditionESQueryBuilder
         return null;
     }
 
-    private void checkRequiredValuesSize(List<?> values, String name, String 
operator, int expectedSize) {
+    private void checkRequiredValuesSize(Collection<?> values, String name, 
String operator, int expectedSize) {
         if (values == null || values.size() != expectedSize) {
             throw new IllegalArgumentException("Impossible to build ES filter, 
missing " + expectedSize + " values for a condition using comparisonOperator: " 
+ operator + ", and propertyName: " + name);
         }
@@ -205,7 +203,7 @@ public class PropertyConditionESQueryBuilder implements 
ConditionESQueryBuilder
         }
     }
 
-    private List<?> convertDatesToISO(List<?> datesValues) {
+    private Collection<?> convertDatesToISO(Collection<?> datesValues) {
         List<Object> results = new ArrayList<>();
         if (datesValues == null) {
             return null;
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 f637a7b..064a460 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
@@ -88,16 +88,16 @@ public class PropertyConditionEvaluator implements 
ConditionEvaluator {
         }
     }
 
-    private boolean compareMultivalue(Object actualValue, List<?> 
expectedValues, List<?> expectedValuesDate, List<?> expectedValuesNumber, 
List<?> expectedValuesDateExpr, String op) {
+    private boolean compareMultivalue(Object actualValue, Collection<?> 
expectedValues, Collection<?> expectedValuesDate, Collection<?> 
expectedValuesNumber, Collection<?> expectedValuesDateExpr, String op) {
         @SuppressWarnings("unchecked")
-        List<?> expected = ObjectUtils.firstNonNull(expectedValues, 
expectedValuesDate, expectedValuesNumber);
+        Collection<?> expected = ObjectUtils.firstNonNull(expectedValues, 
expectedValuesDate, expectedValuesNumber);
         if (actualValue == null) {
             return expected == null;
         } else if (expected == null) {
             return false;
         }
 
-        List<Object> actual = 
ConditionContextHelper.foldToASCII(getValueSet(actualValue));
+        Collection<Object> actual = 
ConditionContextHelper.foldToASCII(getValueSet(actualValue));
 
         boolean result = true;
 
@@ -236,21 +236,21 @@ public class PropertyConditionEvaluator implements 
ConditionEvaluator {
         } else if (op.equals("lessThanOrEqualTo")) {
             return compare(actualValue, expectedValue, expectedValueDate, 
expectedValueInteger, expectedValueDateExpr, expectedValueDouble) <= 0;
         } else if (op.equals("between")) {
-            List<?> expectedValuesInteger = (List<?>) 
condition.getParameter("propertyValuesInteger");
-            List<?> expectedValuesDouble = (List<?>) 
condition.getParameter("propertyValuesDouble");
-            List<?> expectedValuesDate = (List<?>) 
condition.getParameter("propertyValuesDate");
-            List<?> expectedValuesDateExpr = (List<?>) 
condition.getParameter("propertyValuesDateExpr");
+            Collection<?> expectedValuesInteger = (Collection<?>) 
condition.getParameter("propertyValuesInteger");
+            Collection<?> expectedValuesDouble = (Collection<?>) 
condition.getParameter("propertyValuesDouble");
+            Collection<?> expectedValuesDate = (Collection<?>) 
condition.getParameter("propertyValuesDate");
+            Collection<?> expectedValuesDateExpr = (Collection<?>) 
condition.getParameter("propertyValuesDateExpr");
             return compare(actualValue, null,
-                    (expectedValuesDate != null && expectedValuesDate.size() 
>= 1) ? getDate(expectedValuesDate.get(0)) : null,
-                    (expectedValuesInteger != null && 
expectedValuesInteger.size() >= 1) ? (Integer) expectedValuesInteger.get(0) : 
null,
-                    (expectedValuesDateExpr != null && 
expectedValuesDateExpr.size() >= 1) ? (String) expectedValuesDateExpr.get(0) : 
null,
-                    (expectedValuesDouble != null && 
expectedValuesDouble.size() >= 1) ? (String) expectedValuesDouble.get(0) : 
null) >= 0
+                    getDate(getFirst(expectedValuesDate)),
+                    getFirst(expectedValuesInteger),
+                    getFirst(expectedValuesDateExpr),
+                    getFirst(expectedValuesDouble)) >= 0
                     &&
                     compare(actualValue, null,
-                            (expectedValuesDate != null && 
expectedValuesDate.size() >= 2) ? getDate(expectedValuesDate.get(1)) : null,
-                            (expectedValuesInteger != null && 
expectedValuesInteger.size() >= 2) ? (Integer) expectedValuesInteger.get(1) : 
null,
-                            (expectedValuesDateExpr != null && 
expectedValuesDateExpr.size() >= 2) ? (String) expectedValuesDateExpr.get(1) : 
null,
-                            (expectedValuesDouble != null && 
expectedValuesDouble.size() >= 2) ? (String) expectedValuesDouble.get(1) : 
null) <= 0;
+                            getDate(getSecond(expectedValuesDate)),
+                            getSecond(expectedValuesInteger),
+                            getSecond(expectedValuesDateExpr),
+                            getSecond(expectedValuesDouble)) <= 0;
         } else if (op.equals("contains")) {
             return actualValue.toString().contains(expectedValue);
         } else if (op.equals("notContains")) {
@@ -262,10 +262,10 @@ public class PropertyConditionEvaluator implements 
ConditionEvaluator {
         } else if (op.equals("matchesRegex")) {
             return expectedValue != null && 
Pattern.compile(expectedValue).matcher(actualValue.toString()).matches();
         } else if (op.equals("in") || op.equals("inContains") || 
op.equals("notIn") || op.equals("hasSomeOf") || op.equals("hasNoneOf") || 
op.equals("all")) {
-            List<?> expectedValues = 
ConditionContextHelper.foldToASCII((List<?>) 
condition.getParameter("propertyValues"));
-            List<?> expectedValuesInteger = (List<?>) 
condition.getParameter("propertyValuesInteger");
-            List<?> expectedValuesDate = (List<?>) 
condition.getParameter("propertyValuesDate");
-            List<?> expectedValuesDateExpr = (List<?>) 
condition.getParameter("propertyValuesDateExpr");
+            Collection<?> expectedValues = 
ConditionContextHelper.foldToASCII((Collection<?>) 
condition.getParameter("propertyValues"));
+            Collection<?> expectedValuesInteger = (Collection<?>) 
condition.getParameter("propertyValuesInteger");
+            Collection<?> expectedValuesDate = (Collection<?>) 
condition.getParameter("propertyValuesDate");
+            Collection<?> expectedValuesDateExpr = (Collection<?>) 
condition.getParameter("propertyValuesDateExpr");
 
             return compareMultivalue(actualValue, expectedValues, 
expectedValuesDate, expectedValuesInteger, expectedValuesDateExpr, op);
         } else if (op.equals("isDay") && expectedValueDate != null) {
@@ -464,4 +464,35 @@ public class PropertyConditionEvaluator implements 
ConditionEvaluator {
         }
     }
 
+    private Object getFirst(Collection<?> collection) {
+        if (collection == null) {
+            return null;
+        }
+        if (collection.isEmpty()) {
+            return null;
+        }
+        Iterator<?> iterator = collection.iterator();
+        if (iterator.hasNext()) {
+            return iterator.next();
+        }
+        return null;
+    }
+
+    private Object getSecond(Collection<?> collection) {
+        if (collection == null) {
+            return null;
+        }
+        if (collection.isEmpty()) {
+            return null;
+        }
+        Iterator<?> iterator = collection.iterator();
+        if (iterator.hasNext()) {
+            iterator.next();
+            if (iterator.hasNext()) {
+                return iterator.next();
+            }
+        }
+        return null;
+    }
+
 }
diff --git 
a/services/src/main/java/org/apache/unomi/services/actions/impl/ActionExecutorDispatcherImpl.java
 
b/services/src/main/java/org/apache/unomi/services/actions/impl/ActionExecutorDispatcherImpl.java
index 5e4cf61..b54fcdf 100644
--- 
a/services/src/main/java/org/apache/unomi/services/actions/impl/ActionExecutorDispatcherImpl.java
+++ 
b/services/src/main/java/org/apache/unomi/services/actions/impl/ActionExecutorDispatcherImpl.java
@@ -24,6 +24,7 @@ import org.apache.unomi.api.actions.Action;
 import org.apache.unomi.api.actions.ActionDispatcher;
 import org.apache.unomi.api.actions.ActionExecutor;
 import org.apache.unomi.api.services.EventService;
+import org.apache.unomi.api.utils.ParserHelper;
 import org.apache.unomi.metrics.MetricAdapter;
 import org.apache.unomi.metrics.MetricsService;
 import org.apache.unomi.scripting.ScriptExecutor;
@@ -40,10 +41,7 @@ import java.util.concurrent.ConcurrentHashMap;
 
 public class ActionExecutorDispatcherImpl implements ActionExecutorDispatcher {
     private static final Logger logger = 
LoggerFactory.getLogger(ActionExecutorDispatcherImpl.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 final Map<String, ParserHelper.ValueExtractor> valueExtractors = 
new HashMap<>(11);
     private Map<String, ActionExecutor> executors = new ConcurrentHashMap<>();
     private MetricsService metricsService;
     private Map<String, ActionDispatcher> actionDispatchers = new 
ConcurrentHashMap<>();
@@ -63,142 +61,27 @@ public class ActionExecutorDispatcherImpl implements 
ActionExecutorDispatcher {
     }
 
     public ActionExecutorDispatcherImpl() {
-        valueExtractors.put("profileProperty", new ValueExtractor() {
-            @Override
-            public Object extract(String valueAsString, Event event)
-                    throws IllegalAccessException, NoSuchMethodException, 
InvocationTargetException {
-                return PropertyUtils.getProperty(event.getProfile(), 
"properties." + valueAsString);
-            }
-        });
-        valueExtractors.put("simpleProfileProperty", new ValueExtractor() {
-            @Override
-            public Object extract(String valueAsString, Event event)
-                    throws IllegalAccessException, NoSuchMethodException, 
InvocationTargetException {
-                return event.getProfile().getProperty(valueAsString);
-            }
-        });
-        valueExtractors.put("sessionProperty", new ValueExtractor() {
-            @Override
-            public Object extract(String valueAsString, Event event)
-                    throws IllegalAccessException, NoSuchMethodException, 
InvocationTargetException {
-                return PropertyUtils.getProperty(event.getSession(), 
"properties." + valueAsString);
-            }
-        });
-        valueExtractors.put("simpleSessionProperty", new ValueExtractor() {
-            @Override
-            public Object extract(String valueAsString, Event event)
-                    throws IllegalAccessException, NoSuchMethodException, 
InvocationTargetException {
-                return event.getSession().getProperty(valueAsString);
-            }
-        });
-        valueExtractors.put("eventProperty", new ValueExtractor() {
-            @Override
-            public Object extract(String valueAsString, Event event)
-                    throws IllegalAccessException, NoSuchMethodException, 
InvocationTargetException {
-                return PropertyUtils.getProperty(event, valueAsString);
-            }
-        });
-        valueExtractors.put("simpleEventProperty", new ValueExtractor() {
-            @Override
-            public Object extract(String valueAsString, Event event)
-                    throws IllegalAccessException, NoSuchMethodException, 
InvocationTargetException {
-                return event.getProperty(valueAsString);
-            }
-        });
-        valueExtractors.put("script", new ValueExtractor() {
+        valueExtractors.putAll(ParserHelper.DEFAULT_VALUE_EXTRACTORS);
+        valueExtractors.put("script", new ParserHelper.ValueExtractor() {
             @Override
             public Object extract(String valueAsString, Event event)
                     throws IllegalAccessException, NoSuchMethodException, 
InvocationTargetException {
                 return executeScript(valueAsString, event);
             }
-
         });
     }
 
     public Action getContextualAction(Action action, Event event) {
-        if (!hasContextualParameter(action.getParameterValues())) {
+        if (!ParserHelper.hasContextualParameter(action.getParameterValues(), 
valueExtractors)) {
             return action;
         }
 
-        Map<String, Object> values = parseMap(event, 
action.getParameterValues());
+        Map<String, Object> values = ParserHelper.parseMap(event, 
action.getParameterValues(), valueExtractors);
         Action n = new Action(action.getActionType());
         n.setParameterValues(values);
         return n;
     }
 
-    @SuppressWarnings("unchecked")
-    private Map<String, Object> parseMap(Event event, Map<String, Object> map) 
{
-        Map<String, Object> values = new HashMap<>();
-        for (Map.Entry<String, Object> entry : map.entrySet()) {
-            Object value = entry.getValue();
-            if (value instanceof String) {
-                String s = (String) value;
-                try {
-                    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) {
-                    throw e;
-                } catch (Exception e) {
-                    throw new UnsupportedOperationException(e);
-                }
-            } else if (value instanceof Map) {
-                value = parseMap(event, (Map<String, Object>) value);
-            }
-            values.put(entry.getKey(), value);
-        }
-        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;
-                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) {
-                if (hasContextualParameter((Map<String, Object>) value)) {
-                    return true;
-                }
-            }
-        }
-        return false;
-    }
 
     public int execute(Action action, Event event) {
         String actionKey = action.getActionType().getActionExecutor();
@@ -231,10 +114,6 @@ public class ActionExecutorDispatcherImpl implements 
ActionExecutorDispatcher {
         return EventService.NO_CHANGE;
     }
 
-    private interface ValueExtractor {
-        Object extract(String valueAsString, Event event) throws 
IllegalAccessException, NoSuchMethodException, InvocationTargetException;
-    }
-
     public void bindExecutor(ServiceReference<ActionExecutor> 
actionExecutorServiceReference) {
         ActionExecutor actionExecutor = 
bundleContext.getService(actionExecutorServiceReference);
         
executors.put(actionExecutorServiceReference.getProperty("actionExecutorId").toString(),
 actionExecutor);
diff --git 
a/services/src/main/java/org/apache/unomi/services/impl/definitions/DefinitionsServiceImpl.java
 
b/services/src/main/java/org/apache/unomi/services/impl/definitions/DefinitionsServiceImpl.java
index 8cb4e8f..52f41e5 100644
--- 
a/services/src/main/java/org/apache/unomi/services/impl/definitions/DefinitionsServiceImpl.java
+++ 
b/services/src/main/java/org/apache/unomi/services/impl/definitions/DefinitionsServiceImpl.java
@@ -27,7 +27,7 @@ import org.apache.unomi.api.services.DefinitionsService;
 import org.apache.unomi.api.services.SchedulerService;
 import org.apache.unomi.persistence.spi.CustomObjectMapper;
 import org.apache.unomi.persistence.spi.PersistenceService;
-import org.apache.unomi.services.impl.ParserHelper;
+import org.apache.unomi.api.utils.ParserHelper;
 import org.osgi.framework.Bundle;
 import org.osgi.framework.BundleContext;
 import org.osgi.framework.BundleEvent;
diff --git 
a/services/src/main/java/org/apache/unomi/services/impl/events/EventServiceImpl.java
 
b/services/src/main/java/org/apache/unomi/services/impl/events/EventServiceImpl.java
index b831e05..7686797 100644
--- 
a/services/src/main/java/org/apache/unomi/services/impl/events/EventServiceImpl.java
+++ 
b/services/src/main/java/org/apache/unomi/services/impl/events/EventServiceImpl.java
@@ -34,7 +34,7 @@ import org.apache.unomi.api.query.Query;
 import org.apache.unomi.api.services.*;
 import org.apache.unomi.persistence.spi.PersistenceService;
 import org.apache.unomi.persistence.spi.aggregate.TermsAggregate;
-import org.apache.unomi.services.impl.ParserHelper;
+import org.apache.unomi.api.utils.ParserHelper;
 import org.osgi.framework.BundleContext;
 import org.osgi.framework.ServiceReference;
 import org.slf4j.Logger;
diff --git 
a/services/src/main/java/org/apache/unomi/services/impl/goals/GoalsServiceImpl.java
 
b/services/src/main/java/org/apache/unomi/services/impl/goals/GoalsServiceImpl.java
index fcf3afc..ad8e512 100644
--- 
a/services/src/main/java/org/apache/unomi/services/impl/goals/GoalsServiceImpl.java
+++ 
b/services/src/main/java/org/apache/unomi/services/impl/goals/GoalsServiceImpl.java
@@ -37,7 +37,7 @@ import org.apache.unomi.api.services.RulesService;
 import org.apache.unomi.persistence.spi.CustomObjectMapper;
 import org.apache.unomi.persistence.spi.PersistenceService;
 import org.apache.unomi.persistence.spi.aggregate.*;
-import org.apache.unomi.services.impl.ParserHelper;
+import org.apache.unomi.api.utils.ParserHelper;
 import org.osgi.framework.Bundle;
 import org.osgi.framework.BundleContext;
 import org.osgi.framework.BundleEvent;
diff --git 
a/services/src/main/java/org/apache/unomi/services/impl/profiles/ProfileServiceImpl.java
 
b/services/src/main/java/org/apache/unomi/services/impl/profiles/ProfileServiceImpl.java
index b241c44..148a98d 100644
--- 
a/services/src/main/java/org/apache/unomi/services/impl/profiles/ProfileServiceImpl.java
+++ 
b/services/src/main/java/org/apache/unomi/services/impl/profiles/ProfileServiceImpl.java
@@ -43,7 +43,7 @@ import org.apache.unomi.api.services.SegmentService;
 import org.apache.unomi.persistence.spi.CustomObjectMapper;
 import org.apache.unomi.persistence.spi.PersistenceService;
 import org.apache.unomi.persistence.spi.PropertyHelper;
-import org.apache.unomi.services.impl.ParserHelper;
+import org.apache.unomi.api.utils.ParserHelper;
 import org.osgi.framework.Bundle;
 import org.osgi.framework.BundleContext;
 import org.osgi.framework.BundleEvent;
diff --git 
a/services/src/main/java/org/apache/unomi/services/impl/queries/QueryServiceImpl.java
 
b/services/src/main/java/org/apache/unomi/services/impl/queries/QueryServiceImpl.java
index c9317ee..fa1799c 100644
--- 
a/services/src/main/java/org/apache/unomi/services/impl/queries/QueryServiceImpl.java
+++ 
b/services/src/main/java/org/apache/unomi/services/impl/queries/QueryServiceImpl.java
@@ -23,7 +23,7 @@ import org.apache.unomi.api.services.DefinitionsService;
 import org.apache.unomi.api.services.QueryService;
 import org.apache.unomi.persistence.spi.PersistenceService;
 import org.apache.unomi.persistence.spi.aggregate.*;
-import org.apache.unomi.services.impl.ParserHelper;
+import org.apache.unomi.api.utils.ParserHelper;
 import org.slf4j.Logger;
 import org.slf4j.LoggerFactory;
 
diff --git 
a/services/src/main/java/org/apache/unomi/services/impl/rules/RulesServiceImpl.java
 
b/services/src/main/java/org/apache/unomi/services/impl/rules/RulesServiceImpl.java
index fc8e464..d7b7186 100644
--- 
a/services/src/main/java/org/apache/unomi/services/impl/rules/RulesServiceImpl.java
+++ 
b/services/src/main/java/org/apache/unomi/services/impl/rules/RulesServiceImpl.java
@@ -31,7 +31,7 @@ import org.apache.unomi.api.services.*;
 import org.apache.unomi.persistence.spi.CustomObjectMapper;
 import org.apache.unomi.persistence.spi.PersistenceService;
 import org.apache.unomi.services.actions.ActionExecutorDispatcher;
-import org.apache.unomi.services.impl.ParserHelper;
+import org.apache.unomi.api.utils.ParserHelper;
 import org.osgi.framework.*;
 import org.slf4j.Logger;
 import org.slf4j.LoggerFactory;
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 57dacab..b4bd9e3 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
@@ -42,7 +42,7 @@ import org.apache.unomi.persistence.spi.CustomObjectMapper;
 import org.apache.unomi.persistence.spi.aggregate.TermsAggregate;
 import org.apache.unomi.services.impl.AbstractServiceImpl;
 import org.apache.unomi.services.impl.scheduler.SchedulerServiceImpl;
-import org.apache.unomi.services.impl.ParserHelper;
+import org.apache.unomi.api.utils.ParserHelper;
 import org.apache.unomi.api.exceptions.BadSegmentConditionException;
 import org.osgi.framework.Bundle;
 import org.osgi.framework.BundleContext;
diff --git 
a/services/src/main/resources/META-INF/cxs/events/anonymizeProfile.json 
b/services/src/main/resources/META-INF/cxs/events/anonymizeProfile.json
index 4ba8929..ef8dab2 100644
--- a/services/src/main/resources/META-INF/cxs/events/anonymizeProfile.json
+++ b/services/src/main/resources/META-INF/cxs/events/anonymizeProfile.json
@@ -14,6 +14,10 @@
           "type" : "string"
         },
         {
+          "itemId" : "customItemType",
+          "type" : "string"
+        },
+        {
           "itemId" : "scope",
           "type" : "string"
         },
diff --git 
a/services/src/main/resources/META-INF/cxs/events/articleCompleted.json 
b/services/src/main/resources/META-INF/cxs/events/articleCompleted.json
index cc73736..9e31662 100644
--- a/services/src/main/resources/META-INF/cxs/events/articleCompleted.json
+++ b/services/src/main/resources/META-INF/cxs/events/articleCompleted.json
@@ -18,6 +18,10 @@
           "type" : "string"
         },
         {
+          "itemId" : "customItemType",
+          "type" : "string"
+        },
+        {
           "itemId" : "scope",
           "type" : "string"
         },
@@ -48,6 +52,10 @@
           "type" : "string"
         },
         {
+          "itemId" : "customItemType",
+          "type" : "string"
+        },
+        {
           "itemId" : "scope",
           "type" : "string"
         },
diff --git a/services/src/main/resources/META-INF/cxs/events/form.json 
b/services/src/main/resources/META-INF/cxs/events/form.json
index ace03f5..4551168 100644
--- a/services/src/main/resources/META-INF/cxs/events/form.json
+++ b/services/src/main/resources/META-INF/cxs/events/form.json
@@ -18,6 +18,10 @@
           "type" : "string"
         },
         {
+          "itemId" : "customItemType",
+          "type" : "string"
+        },
+        {
           "itemId" : "scope",
           "type" : "string"
         },
@@ -48,6 +52,10 @@
           "type" : "string"
         },
         {
+          "itemId" : "customItemType",
+          "type" : "string"
+        },
+        {
           "itemId" : "scope",
           "type" : "string"
         },
diff --git a/services/src/main/resources/META-INF/cxs/events/goal.json 
b/services/src/main/resources/META-INF/cxs/events/goal.json
index 57a3f9f..11468cc 100644
--- a/services/src/main/resources/META-INF/cxs/events/goal.json
+++ b/services/src/main/resources/META-INF/cxs/events/goal.json
@@ -18,6 +18,10 @@
           "type" : "string"
         },
         {
+          "itemId" : "customItemType",
+          "type" : "string"
+        },
+        {
           "itemId" : "campaignId",
           "type" : "string"
         },
diff --git a/services/src/main/resources/META-INF/cxs/events/identify.json 
b/services/src/main/resources/META-INF/cxs/events/identify.json
index 0842972..1fb001d 100644
--- a/services/src/main/resources/META-INF/cxs/events/identify.json
+++ b/services/src/main/resources/META-INF/cxs/events/identify.json
@@ -18,6 +18,10 @@
           "type" : "string"
         },
         {
+          "itemId" : "customItemType",
+          "type" : "string"
+        },
+        {
           "itemId" : "scope",
           "type" : "string"
         },
diff --git 
a/services/src/main/resources/META-INF/cxs/events/incrementInterest.json 
b/services/src/main/resources/META-INF/cxs/events/incrementInterest.json
index 0ec4bd3..8604990 100644
--- a/services/src/main/resources/META-INF/cxs/events/incrementInterest.json
+++ b/services/src/main/resources/META-INF/cxs/events/incrementInterest.json
@@ -24,6 +24,10 @@
           "type" : "string"
         },
         {
+          "itemId" : "customItemType",
+          "type" : "string"
+        },
+        {
           "itemId" : "scope",
           "type" : "string"
         },
diff --git a/services/src/main/resources/META-INF/cxs/events/login.json 
b/services/src/main/resources/META-INF/cxs/events/login.json
index 8d146c7..4e66255 100644
--- a/services/src/main/resources/META-INF/cxs/events/login.json
+++ b/services/src/main/resources/META-INF/cxs/events/login.json
@@ -14,6 +14,10 @@
           "type" : "string"
         },
         {
+          "itemId" : "customItemType",
+          "type" : "string"
+        },
+        {
           "itemId" : "scope",
           "type" : "string"
         },
@@ -44,6 +48,10 @@
           "type": "string"
         },
         {
+          "itemId" : "customItemType",
+          "type" : "string"
+        },
+        {
           "itemId": "scope",
           "type": "string"
         },
diff --git a/services/src/main/resources/META-INF/cxs/events/modifyConsent.json 
b/services/src/main/resources/META-INF/cxs/events/modifyConsent.json
index 6cee059..5eec0bf 100644
--- a/services/src/main/resources/META-INF/cxs/events/modifyConsent.json
+++ b/services/src/main/resources/META-INF/cxs/events/modifyConsent.json
@@ -14,6 +14,10 @@
           "type" : "string"
         },
         {
+          "itemId" : "customItemType",
+          "type" : "string"
+        },
+        {
           "itemId" : "scope",
           "type" : "string"
         },
@@ -44,6 +48,10 @@
           "type" : "string"
         },
         {
+          "itemId" : "customItemType",
+          "type" : "string"
+        },
+        {
           "itemId" : "scope",
           "type" : "string"
         },
diff --git 
a/services/src/main/resources/META-INF/cxs/events/profileDeleted.json 
b/services/src/main/resources/META-INF/cxs/events/profileDeleted.json
index c1030ac..62fed55 100644
--- a/services/src/main/resources/META-INF/cxs/events/profileDeleted.json
+++ b/services/src/main/resources/META-INF/cxs/events/profileDeleted.json
@@ -14,6 +14,10 @@
           "type" : "string"
         },
         {
+          "itemId" : "customItemType",
+          "type" : "string"
+        },
+        {
           "itemId" : "scope",
           "type" : "string"
         },
diff --git 
a/services/src/main/resources/META-INF/cxs/events/profileUpdated.json 
b/services/src/main/resources/META-INF/cxs/events/profileUpdated.json
index 424ac28..78c675a 100644
--- a/services/src/main/resources/META-INF/cxs/events/profileUpdated.json
+++ b/services/src/main/resources/META-INF/cxs/events/profileUpdated.json
@@ -14,6 +14,10 @@
           "type" : "string"
         },
         {
+          "itemId" : "customItemType",
+          "type" : "string"
+        },
+        {
           "itemId" : "scope",
           "type" : "string"
         },
diff --git a/services/src/main/resources/META-INF/cxs/events/ruleFired.json 
b/services/src/main/resources/META-INF/cxs/events/ruleFired.json
index 601b793..2a0f4c6 100644
--- a/services/src/main/resources/META-INF/cxs/events/ruleFired.json
+++ b/services/src/main/resources/META-INF/cxs/events/ruleFired.json
@@ -18,6 +18,10 @@
           "type" : "string"
         },
         {
+          "itemId" : "customItemType",
+          "type" : "string"
+        },
+        {
           "itemId" : "linkedItems",
           "type" : "string",
           "multivalued" : true
diff --git a/services/src/main/resources/META-INF/cxs/events/search.json 
b/services/src/main/resources/META-INF/cxs/events/search.json
index 5e60f64..94fb45c 100644
--- a/services/src/main/resources/META-INF/cxs/events/search.json
+++ b/services/src/main/resources/META-INF/cxs/events/search.json
@@ -36,6 +36,10 @@
           "type": "string"
         },
         {
+          "itemId" : "customItemType",
+          "type" : "string"
+        },
+        {
           "itemId": "scope",
           "type": "string"
         },
diff --git 
a/services/src/main/resources/META-INF/cxs/events/sessionCreated.json 
b/services/src/main/resources/META-INF/cxs/events/sessionCreated.json
index 1424c28..0d27647 100644
--- a/services/src/main/resources/META-INF/cxs/events/sessionCreated.json
+++ b/services/src/main/resources/META-INF/cxs/events/sessionCreated.json
@@ -14,6 +14,10 @@
           "type" : "string"
         },
         {
+          "itemId" : "customItemType",
+          "type" : "string"
+        },
+        {
           "itemId" : "scope",
           "type" : "string"
         },
diff --git 
a/services/src/main/resources/META-INF/cxs/events/sessionReassigned.json 
b/services/src/main/resources/META-INF/cxs/events/sessionReassigned.json
index 3ec72ab..d69801b 100644
--- a/services/src/main/resources/META-INF/cxs/events/sessionReassigned.json
+++ b/services/src/main/resources/META-INF/cxs/events/sessionReassigned.json
@@ -18,6 +18,10 @@
           "type" : "string"
         },
         {
+          "itemId" : "customItemType",
+          "type" : "string"
+        },
+        {
           "itemId" : "scope",
           "type" : "string"
         },
diff --git 
a/services/src/main/resources/META-INF/cxs/events/updateProperties.json 
b/services/src/main/resources/META-INF/cxs/events/updateProperties.json
index d4de20c..2be02f4 100644
--- a/services/src/main/resources/META-INF/cxs/events/updateProperties.json
+++ b/services/src/main/resources/META-INF/cxs/events/updateProperties.json
@@ -44,6 +44,10 @@
           "type" : "string"
         },
         {
+          "itemId" : "customItemType",
+          "type" : "string"
+        },
+        {
           "itemId" : "scope",
           "type" : "string"
         },
diff --git a/services/src/main/resources/META-INF/cxs/events/view.json 
b/services/src/main/resources/META-INF/cxs/events/view.json
index 798e24c..a07398f 100644
--- a/services/src/main/resources/META-INF/cxs/events/view.json
+++ b/services/src/main/resources/META-INF/cxs/events/view.json
@@ -18,6 +18,10 @@
           "type" : "string"
         },
         {
+          "itemId" : "customItemType",
+          "type" : "string"
+        },
+        {
           "itemId" : "scope",
           "type" : "string"
         },
@@ -48,6 +52,10 @@
           "type" : "string"
         },
         {
+          "itemId" : "customItemType",
+          "type" : "string"
+        },
+        {
           "itemId" : "scope",
           "type" : "string"
         },

Reply via email to