http://git-wip-us.apache.org/repos/asf/incubator-unomi/blob/dc1d1520/services/src/main/java/org/apache/unomi/services/conditions/initializers/ComparisonOperatorChoiceListInitializer.java
----------------------------------------------------------------------
diff --git 
a/services/src/main/java/org/apache/unomi/services/conditions/initializers/ComparisonOperatorChoiceListInitializer.java
 
b/services/src/main/java/org/apache/unomi/services/conditions/initializers/ComparisonOperatorChoiceListInitializer.java
new file mode 100644
index 0000000..3d98548
--- /dev/null
+++ 
b/services/src/main/java/org/apache/unomi/services/conditions/initializers/ComparisonOperatorChoiceListInitializer.java
@@ -0,0 +1,76 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements.  See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * The ASF licenses this file to You under the Apache License, Version 2.0
+ * (the "License"); you may not use this file except in compliance with
+ * the License.  You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package org.apache.unomi.services.conditions.initializers;
+
+import org.apache.unomi.api.conditions.initializers.ChoiceListInitializer;
+import org.apache.unomi.api.conditions.initializers.ChoiceListValue;
+import org.apache.unomi.api.conditions.initializers.I18nSupport;
+import org.osgi.framework.BundleContext;
+
+import java.util.ArrayList;
+import java.util.List;
+
+/**
+ * Initializer for the set of available comparison operators.
+ */
+public class ComparisonOperatorChoiceListInitializer implements 
ChoiceListInitializer, I18nSupport {
+
+    private List<ChoiceListValue> operators;
+
+    private BundleContext bundleContext;
+
+    @Override
+    public List<ChoiceListValue> getValues(Object context) {
+        return operators;
+    }
+
+    public void setBundleContext(BundleContext bundleContext) {
+        this.bundleContext = bundleContext;
+
+        operators = new ArrayList<>(12);
+        operators.add(new ComparisonOperatorChoiceListValue("equals", 
"comparisonOperator.equals",
+                "string", "integer", "email"));
+        operators.add(new ComparisonOperatorChoiceListValue("notEquals", 
"comparisonOperator.notEquals",
+                "string", "integer", "email"));
+        operators.add(new ComparisonOperatorChoiceListValue("lessThan", 
"comparisonOperator.lessThan", "integer", "date"));
+        operators.add(new ComparisonOperatorChoiceListValue("greaterThan", 
"comparisonOperator.greaterThan", "integer", "date"));
+        operators.add(new 
ComparisonOperatorChoiceListValue("lessThanOrEqualTo", 
"comparisonOperator.lessThanOrEqualTo",
+                "integer", "date"));
+        operators.add(new 
ComparisonOperatorChoiceListValue("greaterThanOrEqualTo", 
"comparisonOperator.greaterThanOrEqualTo",
+                "integer", "date"));
+        operators.add(new ComparisonOperatorChoiceListValue("between", 
"comparisonOperator.between",
+                "integer", "date"));
+        operators.add(new ComparisonOperatorChoiceListValue("startsWith", 
"comparisonOperator.startsWith", "string", "email"));
+        operators.add(new ComparisonOperatorChoiceListValue("endsWith", 
"comparisonOperator.endsWith", "string", "email"));
+        operators.add(new ComparisonOperatorChoiceListValue("matchesRegex", 
"comparisonOperator.matchesRegularExpression",
+                "string", "email"));
+        operators.add(new ComparisonOperatorChoiceListValue("contains", 
"comparisonOperator.contains", "string", "email"));
+        operators.add(new ComparisonOperatorChoiceListValue("exists", 
"comparisonOperator.exists"));
+        operators.add(new ComparisonOperatorChoiceListValue("missing", 
"comparisonOperator.missing"));
+        operators.add(new ComparisonOperatorChoiceListValue("in", 
"comparisonOperator.in", "string", "integer", "email"));
+        operators.add(new ComparisonOperatorChoiceListValue("notIn", 
"comparisonOperator.notIn", "string", "integer", "email"));
+        operators.add(new ComparisonOperatorChoiceListValue("all", 
"comparisonOperator.all"));
+        operators.add(new ComparisonOperatorChoiceListValue("isDay", 
"comparisonOperator.isDay", "date"));
+        operators.add(new ComparisonOperatorChoiceListValue("isNotDay", 
"comparisonOperator.isNotDay", "date"));
+
+        for (ChoiceListValue op : operators) {
+            ((ComparisonOperatorChoiceListValue) 
op).setPluginId(bundleContext.getBundle().getBundleId());
+        }
+
+    }
+}

http://git-wip-us.apache.org/repos/asf/incubator-unomi/blob/dc1d1520/services/src/main/java/org/apache/unomi/services/conditions/initializers/ComparisonOperatorChoiceListValue.java
----------------------------------------------------------------------
diff --git 
a/services/src/main/java/org/apache/unomi/services/conditions/initializers/ComparisonOperatorChoiceListValue.java
 
b/services/src/main/java/org/apache/unomi/services/conditions/initializers/ComparisonOperatorChoiceListValue.java
new file mode 100644
index 0000000..d2fc050
--- /dev/null
+++ 
b/services/src/main/java/org/apache/unomi/services/conditions/initializers/ComparisonOperatorChoiceListValue.java
@@ -0,0 +1,89 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements.  See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * The ASF licenses this file to You under the Apache License, Version 2.0
+ * (the "License"); you may not use this file except in compliance with
+ * the License.  You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package org.apache.unomi.services.conditions.initializers;
+
+import org.apache.unomi.api.PluginType;
+import org.apache.unomi.api.conditions.initializers.ChoiceListValue;
+
+import java.util.Collections;
+import java.util.HashSet;
+import java.util.Set;
+
+/**
+ * Choice list value for the comparison operator, which also includes the 
information about applicable value types for the operator.
+ * 
+ * @author Sergiy Shyrkov
+ */
+public class ComparisonOperatorChoiceListValue extends ChoiceListValue 
implements PluginType {
+
+    private Set<String> appliesTo = Collections.emptySet();
+
+    private long pluginId;
+
+    /**
+     * Initializes an instance of this class.
+     * 
+     * @param id
+     *            the ID of the property
+     * @param name
+     *            the display name
+     */
+    public ComparisonOperatorChoiceListValue(String id, String name) {
+        super(id, name);
+    }
+
+    /**
+     * Initializes an instance of this class.
+     * 
+     * @param id
+     *            the ID of the property
+     * @param name
+     *            the display name
+     * @param appliesTo
+     *            array of value types this operator applies to; if not 
specified the operator is applicable for all value types
+     */
+    public ComparisonOperatorChoiceListValue(String id, String name, String... 
appliesTo) {
+        this(id, name);
+        if (appliesTo != null && appliesTo.length > 0) {
+            this.appliesTo = new HashSet<>(appliesTo.length);
+            for (String at : appliesTo) {
+                this.appliesTo.add(at);
+            }
+        }
+    }
+
+    /**
+     * Returns a set of value types this comparison operator is applicable to. 
Returns an empty set in case there are no type restrictions,
+     * i.e. operator can be applied to any value type.
+     * 
+     * @return a set of value types this comparison operator is applicable to. 
Returns an empty set in case there are no type restrictions,
+     *         i.e. operator can be applied to any value type
+     */
+    public Set<String> getAppliesTo() {
+        return appliesTo;
+    }
+
+    @Override
+    public long getPluginId() {
+        return pluginId;
+    }
+
+    @Override
+    public void setPluginId(long pluginId) {
+        this.pluginId = pluginId;
+    }
+}

http://git-wip-us.apache.org/repos/asf/incubator-unomi/blob/dc1d1520/services/src/main/java/org/apache/unomi/services/conditions/initializers/CountryChoiceListInitializer.java
----------------------------------------------------------------------
diff --git 
a/services/src/main/java/org/apache/unomi/services/conditions/initializers/CountryChoiceListInitializer.java
 
b/services/src/main/java/org/apache/unomi/services/conditions/initializers/CountryChoiceListInitializer.java
new file mode 100644
index 0000000..c63e406
--- /dev/null
+++ 
b/services/src/main/java/org/apache/unomi/services/conditions/initializers/CountryChoiceListInitializer.java
@@ -0,0 +1,68 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements.  See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * The ASF licenses this file to You under the Apache License, Version 2.0
+ * (the "License"); you may not use this file except in compliance with
+ * the License.  You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package org.apache.unomi.services.conditions.initializers;
+
+import org.apache.unomi.api.conditions.initializers.ChoiceListInitializer;
+import org.apache.unomi.api.conditions.initializers.ChoiceListValue;
+
+import java.util.*;
+
+/**
+ * Initializer for the set of known countries.
+ * 
+ * @author Sergiy Shyrkov
+ */
+public class CountryChoiceListInitializer implements ChoiceListInitializer {
+
+    private static final Comparator<ChoiceListValue> NAME_COMPARATOR = new 
Comparator<ChoiceListValue>() {
+        @Override
+        public int compare(ChoiceListValue o1, ChoiceListValue o2) {
+            // we do not need to deal with null values, so make it straight
+            return o1.getName().compareTo(o2.getName());
+        }
+    };
+
+    private Map<String, Locale> countryLocales;
+
+    private Map<String, Locale> getContryLocales() {
+        if (countryLocales == null) {
+            Map<String, Locale> l = new HashMap<>();
+            String[] countryCodes = Locale.getISOCountries();
+            for (String code : countryCodes) {
+                l.put(code, new Locale("en", code));
+            }
+            countryLocales = l;
+        }
+
+        return countryLocales;
+    }
+
+    @Override
+    public List<ChoiceListValue> getValues(Object context) {
+        Locale locale = context instanceof Locale ? (Locale) context : 
Locale.ENGLISH;
+
+        Map<String, Locale> locales = getContryLocales();
+        List<ChoiceListValue> options = new 
ArrayList<ChoiceListValue>(locales.size());
+        for (Map.Entry<String, Locale> entry : locales.entrySet()) {
+            options.add(new ChoiceListValue(entry.getKey(), 
entry.getValue().getDisplayCountry(locale)));
+        }
+        Collections.sort(options, NAME_COMPARATOR);
+
+        return options;
+    }
+}

http://git-wip-us.apache.org/repos/asf/incubator-unomi/blob/dc1d1520/services/src/main/java/org/apache/unomi/services/conditions/initializers/EventPropertyChoiceListInitializer.java
----------------------------------------------------------------------
diff --git 
a/services/src/main/java/org/apache/unomi/services/conditions/initializers/EventPropertyChoiceListInitializer.java
 
b/services/src/main/java/org/apache/unomi/services/conditions/initializers/EventPropertyChoiceListInitializer.java
new file mode 100644
index 0000000..0a8a04e
--- /dev/null
+++ 
b/services/src/main/java/org/apache/unomi/services/conditions/initializers/EventPropertyChoiceListInitializer.java
@@ -0,0 +1,47 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements.  See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * The ASF licenses this file to You under the Apache License, Version 2.0
+ * (the "License"); you may not use this file except in compliance with
+ * the License.  You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package org.apache.unomi.services.conditions.initializers;
+
+import org.apache.unomi.api.EventProperty;
+import org.apache.unomi.api.conditions.initializers.ChoiceListInitializer;
+import org.apache.unomi.api.conditions.initializers.ChoiceListValue;
+import org.apache.unomi.api.services.EventService;
+
+import java.util.ArrayList;
+import java.util.List;
+
+/**
+ * Initializer for the set of available event properties.
+ */
+public class EventPropertyChoiceListInitializer implements 
ChoiceListInitializer {
+
+    EventService eventService;
+
+    public List<ChoiceListValue> getValues(Object context) {
+        List<EventProperty> eventProperties = 
eventService.getEventProperties();
+        List<ChoiceListValue> choiceListValues = new 
ArrayList<>(eventProperties.size());
+        for (EventProperty eventProperty : eventProperties) {
+            choiceListValues.add(new 
PropertyTypeChoiceListValue(eventProperty.getId(), eventProperty.getId(), 
eventProperty.getValueType()));
+        }
+        return choiceListValues;
+    }
+
+    public void setEventService(EventService eventService) {
+        this.eventService = eventService;
+    }
+}

http://git-wip-us.apache.org/repos/asf/incubator-unomi/blob/dc1d1520/services/src/main/java/org/apache/unomi/services/conditions/initializers/EventTypeIdChoiceListInitializer.java
----------------------------------------------------------------------
diff --git 
a/services/src/main/java/org/apache/unomi/services/conditions/initializers/EventTypeIdChoiceListInitializer.java
 
b/services/src/main/java/org/apache/unomi/services/conditions/initializers/EventTypeIdChoiceListInitializer.java
new file mode 100644
index 0000000..005d19e
--- /dev/null
+++ 
b/services/src/main/java/org/apache/unomi/services/conditions/initializers/EventTypeIdChoiceListInitializer.java
@@ -0,0 +1,46 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements.  See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * The ASF licenses this file to You under the Apache License, Version 2.0
+ * (the "License"); you may not use this file except in compliance with
+ * the License.  You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package org.apache.unomi.services.conditions.initializers;
+
+import org.apache.unomi.api.conditions.initializers.ChoiceListInitializer;
+import org.apache.unomi.api.conditions.initializers.ChoiceListValue;
+import org.apache.unomi.api.conditions.initializers.I18nSupport;
+import org.apache.unomi.api.services.EventService;
+
+import java.util.ArrayList;
+import java.util.List;
+import java.util.Set;
+
+public class EventTypeIdChoiceListInitializer implements 
ChoiceListInitializer, I18nSupport {
+
+    EventService eventService;
+
+    public void setEventService(EventService eventService) {
+        this.eventService = eventService;
+    }
+
+    public List<ChoiceListValue> getValues(Object context) {
+        List<ChoiceListValue> choiceListValues = new 
ArrayList<ChoiceListValue>();
+        Set<String> eventTypeIds = eventService.getEventTypeIds();
+        for (String eventProperty : eventTypeIds) {
+            String resourceKey = "eventType." + eventProperty;
+            choiceListValues.add(new ChoiceListValue(eventProperty, 
resourceKey));
+        }
+        return choiceListValues;
+    }
+}

http://git-wip-us.apache.org/repos/asf/incubator-unomi/blob/dc1d1520/services/src/main/java/org/apache/unomi/services/conditions/initializers/GoalsChoiceListInitializer.java
----------------------------------------------------------------------
diff --git 
a/services/src/main/java/org/apache/unomi/services/conditions/initializers/GoalsChoiceListInitializer.java
 
b/services/src/main/java/org/apache/unomi/services/conditions/initializers/GoalsChoiceListInitializer.java
new file mode 100644
index 0000000..5f9a309
--- /dev/null
+++ 
b/services/src/main/java/org/apache/unomi/services/conditions/initializers/GoalsChoiceListInitializer.java
@@ -0,0 +1,44 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements.  See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * The ASF licenses this file to You under the Apache License, Version 2.0
+ * (the "License"); you may not use this file except in compliance with
+ * the License.  You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package org.apache.unomi.services.conditions.initializers;
+
+import org.apache.unomi.api.Metadata;
+import org.apache.unomi.api.conditions.initializers.ChoiceListInitializer;
+import org.apache.unomi.api.conditions.initializers.ChoiceListValue;
+import org.apache.unomi.api.services.GoalsService;
+
+import java.util.ArrayList;
+import java.util.List;
+
+public class GoalsChoiceListInitializer implements ChoiceListInitializer {
+
+    private GoalsService goalsService;
+
+    public void setGoalsService(GoalsService goalsService) {
+        this.goalsService = goalsService;
+    }
+
+    @Override
+    public List<ChoiceListValue> getValues(Object context) {
+        List<ChoiceListValue> r = new ArrayList<>();
+        for (Metadata metadata : goalsService.getGoalMetadatas()) {
+            r.add(new ChoiceListValue(metadata.getId(), metadata.getName()));
+        }
+        return r;
+    }
+}

http://git-wip-us.apache.org/repos/asf/incubator-unomi/blob/dc1d1520/services/src/main/java/org/apache/unomi/services/conditions/initializers/PropertyTypeChoiceListValue.java
----------------------------------------------------------------------
diff --git 
a/services/src/main/java/org/apache/unomi/services/conditions/initializers/PropertyTypeChoiceListValue.java
 
b/services/src/main/java/org/apache/unomi/services/conditions/initializers/PropertyTypeChoiceListValue.java
new file mode 100644
index 0000000..97809f5
--- /dev/null
+++ 
b/services/src/main/java/org/apache/unomi/services/conditions/initializers/PropertyTypeChoiceListValue.java
@@ -0,0 +1,135 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements.  See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * The ASF licenses this file to You under the Apache License, Version 2.0
+ * (the "License"); you may not use this file except in compliance with
+ * the License.  You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package org.apache.unomi.services.conditions.initializers;
+
+import org.apache.unomi.api.PluginType;
+import org.apache.unomi.api.conditions.initializers.ChoiceListValue;
+
+/**
+ * Choice list value for the properties, which also includes the required 
value type of the property.
+ * 
+ * @author Sergiy Shyrkov
+ */
+public class PropertyTypeChoiceListValue extends ChoiceListValue implements 
PluginType {
+
+    private boolean multivalued;
+
+    private String valueType = "string";
+
+    private long pluginId;
+
+    /**
+     * Initializes an instance of this class.
+     * 
+     * @param id
+     *            the ID of the property
+     * @param name
+     *            the display name
+     * @param valueType
+     *            the required property value type
+     */
+    public PropertyTypeChoiceListValue(String id, String name, String 
valueType) {
+        this(id, name, valueType, false);
+    }
+
+    /**
+     * Initializes an instance of this class.
+     * 
+     * @param id
+     *            the ID of the property
+     * @param name
+     *            the display name
+     * @param valueType
+     *            the required property value type
+     * @param multivalued
+     *            {@code true} if the property supports multiple values; 
{@code false} - in case it is a single value property
+     */
+    public PropertyTypeChoiceListValue(String id, String name, String 
valueType, boolean multivalued) {
+        super(id, name);
+        this.valueType = valueType;
+        this.multivalued = multivalued;
+    }
+
+    /**
+     * Initializes an instance of this class.
+     *
+     * @param id
+     *            the ID of the property
+     * @param name
+     *            the display name
+     * @param valueType
+     *            the required property value type
+     * @param multivalued
+     *            {@code true} if the property supports multiple values; 
{@code false} - in case it is a single value property
+     * @param pluginId
+     *            the PropertyType PluginId to retrieve bundle
+     */
+    public PropertyTypeChoiceListValue(String id, String name, String 
valueType, boolean multivalued, long pluginId) {
+        super(id, name);
+        this.multivalued = multivalued;
+        this.valueType = valueType;
+        this.pluginId = pluginId;
+    }
+
+    /**
+     * Returns the required property value type.
+     * 
+     * @return the required property value type
+     */
+    public String getValueType() {
+        return valueType;
+    }
+
+    /**
+     * Sets the required property value type.
+     *
+     * @param valueType
+     *            the required value type for this property
+     */
+    public void setValueType(String valueType) {
+        this.valueType = valueType;
+    }
+
+    /**
+     * Indicates if the property supports multiple values.
+     *
+     * @return {@code true} if the property supports multiple values; {@code 
false} - in case it is a single value property
+     */
+    public boolean isMultivalued() {
+        return multivalued;
+    }
+
+    /**
+     * Sets the indicator if the property supports multiple values.
+     *
+     * @param multivalued
+     *            {@code true} if the property supports multiple values; 
{@code false} - in case it is a single value property
+     */
+    public void setMultivalued(boolean multivalued) {
+        this.multivalued = multivalued;
+    }
+
+    @Override
+    public long getPluginId() {
+        return pluginId;
+    }
+
+    @Override
+    public void setPluginId(long pluginId) {
+        this.pluginId = pluginId;
+    }
+}

http://git-wip-us.apache.org/repos/asf/incubator-unomi/blob/dc1d1520/services/src/main/java/org/apache/unomi/services/conditions/initializers/SegmentsChoiceListInitializer.java
----------------------------------------------------------------------
diff --git 
a/services/src/main/java/org/apache/unomi/services/conditions/initializers/SegmentsChoiceListInitializer.java
 
b/services/src/main/java/org/apache/unomi/services/conditions/initializers/SegmentsChoiceListInitializer.java
new file mode 100644
index 0000000..4b3a9cb
--- /dev/null
+++ 
b/services/src/main/java/org/apache/unomi/services/conditions/initializers/SegmentsChoiceListInitializer.java
@@ -0,0 +1,47 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements.  See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * The ASF licenses this file to You under the Apache License, Version 2.0
+ * (the "License"); you may not use this file except in compliance with
+ * the License.  You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package org.apache.unomi.services.conditions.initializers;
+
+import org.apache.unomi.api.Metadata;
+import org.apache.unomi.api.conditions.initializers.ChoiceListInitializer;
+import org.apache.unomi.api.conditions.initializers.ChoiceListValue;
+import org.apache.unomi.api.services.SegmentService;
+
+import java.util.ArrayList;
+import java.util.List;
+
+/**
+ * Initializer for segment choice list.
+ */
+public class SegmentsChoiceListInitializer implements ChoiceListInitializer {
+
+    SegmentService segmentService;
+
+    public void setSegmentService(SegmentService segmentService) {
+        this.segmentService = segmentService;
+    }
+
+    public List<ChoiceListValue> getValues(Object context) {
+        List<ChoiceListValue> choiceListValues = new 
ArrayList<ChoiceListValue>();
+        List<Metadata> profileProperties = 
segmentService.getSegmentMetadatas(0, 50, null).getList();
+        for (Metadata profileProperty : profileProperties) {
+            choiceListValues.add(new ChoiceListValue(profileProperty.getId(), 
profileProperty.getName()));
+        }
+        return choiceListValues;
+    }
+}

http://git-wip-us.apache.org/repos/asf/incubator-unomi/blob/dc1d1520/services/src/main/java/org/apache/unomi/services/mergers/AddPropertyMergeStrategyExecutor.java
----------------------------------------------------------------------
diff --git 
a/services/src/main/java/org/apache/unomi/services/mergers/AddPropertyMergeStrategyExecutor.java
 
b/services/src/main/java/org/apache/unomi/services/mergers/AddPropertyMergeStrategyExecutor.java
new file mode 100644
index 0000000..3231567
--- /dev/null
+++ 
b/services/src/main/java/org/apache/unomi/services/mergers/AddPropertyMergeStrategyExecutor.java
@@ -0,0 +1,78 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements.  See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * The ASF licenses this file to You under the Apache License, Version 2.0
+ * (the "License"); you may not use this file except in compliance with
+ * the License.  You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package org.apache.unomi.services.mergers;
+
+import org.apache.unomi.api.Profile;
+import org.apache.unomi.api.PropertyMergeStrategyExecutor;
+import org.apache.unomi.api.PropertyType;
+
+import java.util.List;
+
+public class AddPropertyMergeStrategyExecutor implements 
PropertyMergeStrategyExecutor {
+    public boolean mergeProperty(String propertyName, PropertyType 
propertyType, List<Profile> profilesToMerge, Profile targetProfile) {
+        Object targetPropertyValue = targetProfile.getProperty(propertyName);
+        Object result = targetPropertyValue;
+        if (result == null) {
+            if (propertyType.getValueTypeId() != null) {
+                if (propertyType.getValueTypeId().equals("integer")) {
+                    result = new Integer(0);
+                } else if (propertyType.getValueTypeId().equals("long")) {
+                    result = new Long(0);
+                } else if (propertyType.getValueTypeId().equals("double")) {
+                    result = new Double(0.0);
+                } else if (propertyType.getValueTypeId().equals("float")) {
+                    result = new Float(0.0);
+                } else {
+                    result = new Long(0);
+                }
+            } else {
+                result = new Long(0);
+            }
+        }
+
+        for (Profile profileToMerge : profilesToMerge) {
+
+            Object property = profileToMerge.getProperty(propertyName);
+            if (property == null) {
+                continue;
+            }
+
+            if (propertyType != null) {
+                if (propertyType.getValueTypeId().equals("integer") || 
(property instanceof Integer)) {
+                    result = (Integer) result + (Integer) property;
+                } else if (propertyType.getValueTypeId().equals("long") || 
(property instanceof Long)) {
+                    result = (Long) result + (Long) property;
+                } else if (propertyType.getValueTypeId().equals("double") || 
(property instanceof Double)) {
+                    result = (Double) result + (Double) property;
+                } else if (propertyType.getValueTypeId().equals("float") || 
(property instanceof Float)) {
+                    result = (Float) result + (Float) property;
+                } else {
+                    result = (Long) result + 
Long.parseLong(property.toString());
+                }
+            } else {
+                result = (Long) result + Long.parseLong(property.toString());
+            }
+
+        }
+        if (targetPropertyValue == null || 
!targetPropertyValue.equals(result)) {
+            targetProfile.setProperty(propertyName, result);
+            return true;
+        }
+        return false;
+    }
+}

http://git-wip-us.apache.org/repos/asf/incubator-unomi/blob/dc1d1520/services/src/main/java/org/apache/unomi/services/mergers/DefaultPropertyMergeStrategyExecutor.java
----------------------------------------------------------------------
diff --git 
a/services/src/main/java/org/apache/unomi/services/mergers/DefaultPropertyMergeStrategyExecutor.java
 
b/services/src/main/java/org/apache/unomi/services/mergers/DefaultPropertyMergeStrategyExecutor.java
new file mode 100644
index 0000000..e77f7e3
--- /dev/null
+++ 
b/services/src/main/java/org/apache/unomi/services/mergers/DefaultPropertyMergeStrategyExecutor.java
@@ -0,0 +1,38 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements.  See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * The ASF licenses this file to You under the Apache License, Version 2.0
+ * (the "License"); you may not use this file except in compliance with
+ * the License.  You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package org.apache.unomi.services.mergers;
+
+import org.apache.unomi.api.Profile;
+import org.apache.unomi.api.PropertyMergeStrategyExecutor;
+import org.apache.unomi.api.PropertyType;
+
+import java.util.List;
+
+public class DefaultPropertyMergeStrategyExecutor implements 
PropertyMergeStrategyExecutor {
+    public boolean mergeProperty(String propertyName, PropertyType 
propertyType, List<Profile> profilesToMerge, Profile targetProfile) {
+        boolean modified = false;
+        for (Profile profileToMerge : profilesToMerge) {
+            if (profileToMerge.getProperty(propertyName) != null &&
+                    
profileToMerge.getProperty(propertyName).toString().length() > 0) {
+                targetProfile.setProperty(propertyName, 
profileToMerge.getProperty(propertyName));
+                modified = true;
+            }
+        }
+        return modified;
+    }
+}

http://git-wip-us.apache.org/repos/asf/incubator-unomi/blob/dc1d1520/services/src/main/java/org/apache/unomi/services/mergers/MostRecentPropertyMergeStrategyExecutor.java
----------------------------------------------------------------------
diff --git 
a/services/src/main/java/org/apache/unomi/services/mergers/MostRecentPropertyMergeStrategyExecutor.java
 
b/services/src/main/java/org/apache/unomi/services/mergers/MostRecentPropertyMergeStrategyExecutor.java
new file mode 100644
index 0000000..451da2c
--- /dev/null
+++ 
b/services/src/main/java/org/apache/unomi/services/mergers/MostRecentPropertyMergeStrategyExecutor.java
@@ -0,0 +1,40 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements.  See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * The ASF licenses this file to You under the Apache License, Version 2.0
+ * (the "License"); you may not use this file except in compliance with
+ * the License.  You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package org.apache.unomi.services.mergers;
+
+import org.apache.unomi.api.Profile;
+import org.apache.unomi.api.PropertyMergeStrategyExecutor;
+import org.apache.unomi.api.PropertyType;
+
+import java.util.List;
+
+public class MostRecentPropertyMergeStrategyExecutor implements 
PropertyMergeStrategyExecutor {
+    public boolean mergeProperty(String propertyName, PropertyType 
propertyType, List<Profile> profilesToMerge, Profile targetProfile) {
+        Object result = null;
+        int i = profilesToMerge.size() - 1;
+        while (result == null && i >=0) {
+            result = profilesToMerge.get(i).getProperty(propertyName);
+            i--;
+        }
+        if (result != null && (targetProfile.getProperty(propertyName) == null 
|| !result.equals(targetProfile.getProperty(propertyName)))) {
+            targetProfile.setProperty(propertyName, result);
+            return true;
+        }
+        return false;
+    }
+}

http://git-wip-us.apache.org/repos/asf/incubator-unomi/blob/dc1d1520/services/src/main/java/org/apache/unomi/services/mergers/NonEmptyPropertyMergeStrategyExecutor.java
----------------------------------------------------------------------
diff --git 
a/services/src/main/java/org/apache/unomi/services/mergers/NonEmptyPropertyMergeStrategyExecutor.java
 
b/services/src/main/java/org/apache/unomi/services/mergers/NonEmptyPropertyMergeStrategyExecutor.java
new file mode 100644
index 0000000..44c026c
--- /dev/null
+++ 
b/services/src/main/java/org/apache/unomi/services/mergers/NonEmptyPropertyMergeStrategyExecutor.java
@@ -0,0 +1,30 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements.  See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * The ASF licenses this file to You under the Apache License, Version 2.0
+ * (the "License"); you may not use this file except in compliance with
+ * the License.  You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package org.apache.unomi.services.mergers;
+
+import org.apache.unomi.api.Profile;
+import org.apache.unomi.api.PropertyMergeStrategyExecutor;
+import org.apache.unomi.api.PropertyType;
+
+import java.util.List;
+
+public class NonEmptyPropertyMergeStrategyExecutor implements 
PropertyMergeStrategyExecutor {
+    public boolean mergeProperty(String propertyName, PropertyType 
propertyType, List<Profile> profilesToMerge, Profile targetProfile) {
+        return false;
+    }
+}

http://git-wip-us.apache.org/repos/asf/incubator-unomi/blob/dc1d1520/services/src/main/java/org/apache/unomi/services/mergers/OldestPropertyMergeStrategyExecutor.java
----------------------------------------------------------------------
diff --git 
a/services/src/main/java/org/apache/unomi/services/mergers/OldestPropertyMergeStrategyExecutor.java
 
b/services/src/main/java/org/apache/unomi/services/mergers/OldestPropertyMergeStrategyExecutor.java
new file mode 100644
index 0000000..0d877fa
--- /dev/null
+++ 
b/services/src/main/java/org/apache/unomi/services/mergers/OldestPropertyMergeStrategyExecutor.java
@@ -0,0 +1,30 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements.  See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * The ASF licenses this file to You under the Apache License, Version 2.0
+ * (the "License"); you may not use this file except in compliance with
+ * the License.  You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package org.apache.unomi.services.mergers;
+
+import org.apache.unomi.api.Profile;
+import org.apache.unomi.api.PropertyMergeStrategyExecutor;
+import org.apache.unomi.api.PropertyType;
+
+import java.util.List;
+
+public class OldestPropertyMergeStrategyExecutor implements 
PropertyMergeStrategyExecutor {
+    public boolean mergeProperty(String propertyName, PropertyType 
propertyType, List<Profile> profilesToMerge, Profile targetProfile) {
+        return false;
+    }
+}

http://git-wip-us.apache.org/repos/asf/incubator-unomi/blob/dc1d1520/services/src/main/java/org/apache/unomi/services/services/DefinitionsServiceImpl.java
----------------------------------------------------------------------
diff --git 
a/services/src/main/java/org/apache/unomi/services/services/DefinitionsServiceImpl.java
 
b/services/src/main/java/org/apache/unomi/services/services/DefinitionsServiceImpl.java
new file mode 100644
index 0000000..f4b39c2
--- /dev/null
+++ 
b/services/src/main/java/org/apache/unomi/services/services/DefinitionsServiceImpl.java
@@ -0,0 +1,456 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements.  See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * The ASF licenses this file to You under the Apache License, Version 2.0
+ * (the "License"); you may not use this file except in compliance with
+ * the License.  You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package org.apache.unomi.services.services;
+
+import org.apache.unomi.api.PluginType;
+import org.apache.unomi.api.PropertyMergeStrategyType;
+import org.apache.unomi.api.Tag;
+import org.apache.unomi.api.ValueType;
+import org.apache.unomi.api.actions.ActionType;
+import org.apache.unomi.api.conditions.Condition;
+import org.apache.unomi.api.conditions.ConditionType;
+import org.apache.unomi.api.services.DefinitionsService;
+import org.apache.unomi.persistence.spi.CustomObjectMapper;
+import org.osgi.framework.Bundle;
+import org.osgi.framework.BundleContext;
+import org.osgi.framework.BundleEvent;
+import org.osgi.framework.SynchronousBundleListener;
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
+
+import java.io.IOException;
+import java.net.URL;
+import java.util.*;
+
+public class DefinitionsServiceImpl implements DefinitionsService, 
SynchronousBundleListener {
+
+    private static final Logger logger = 
LoggerFactory.getLogger(DefinitionsServiceImpl.class.getName());
+
+    private Map<String, Tag> tags = new HashMap<>();
+    private Set<Tag> rootTags = new LinkedHashSet<>();
+    private Map<String, ConditionType> conditionTypeById = new HashMap<>();
+    private Map<String, ActionType> actionTypeById = new HashMap<>();
+    private Map<String, ValueType> valueTypeById = new HashMap<>();
+    private Map<Tag, Set<ConditionType>> conditionTypeByTag = new HashMap<>();
+    private Map<Tag, Set<ActionType>> actionTypeByTag = new HashMap<>();
+    private Map<Tag, Set<ValueType>> valueTypeByTag = new HashMap<>();
+    private Map<Long, List<PluginType>> pluginTypes = new HashMap<>();
+    private Map<String, PropertyMergeStrategyType> 
propertyMergeStrategyTypeById = new HashMap<>();
+
+    private BundleContext bundleContext;
+    public DefinitionsServiceImpl() {
+        logger.info("Instantiating definitions service...");
+    }
+
+    public void setBundleContext(BundleContext bundleContext) {
+        this.bundleContext = bundleContext;
+    }
+
+    public void postConstruct() {
+        logger.debug("postConstruct {" + bundleContext.getBundle() + "}");
+
+        processBundleStartup(bundleContext);
+
+        // process already started bundles
+        for (Bundle bundle : bundleContext.getBundles()) {
+            if (bundle.getBundleContext() != null) {
+                processBundleStartup(bundle.getBundleContext());
+            }
+        }
+
+        bundleContext.addBundleListener(this);
+    }
+
+    private void processBundleStartup(BundleContext bundleContext) {
+        if (bundleContext == null) {
+            return;
+        }
+
+        pluginTypes.put(bundleContext.getBundle().getBundleId(), new 
ArrayList<PluginType>());
+
+        loadPredefinedTags(bundleContext);
+
+        loadPredefinedConditionTypes(bundleContext);
+        loadPredefinedActionTypes(bundleContext);
+        loadPredefinedValueTypes(bundleContext);
+        loadPredefinedPropertyMergeStrategies(bundleContext);
+
+    }
+
+    private void processBundleStop(BundleContext bundleContext) {
+        if (bundleContext == null) {
+            return;
+        }
+        List<PluginType> types = 
pluginTypes.get(bundleContext.getBundle().getBundleId());
+        if (types != null) {
+            for (PluginType type : types) {
+                if (type instanceof ActionType) {
+                    ActionType actionType = (ActionType) type;
+                    actionTypeById.remove(actionType.getId());
+                    for (Tag tag : actionType.getTags()) {
+                        actionTypeByTag.get(tag).remove(actionType);
+                    }
+                } else if (type instanceof ConditionType) {
+                    ConditionType conditionType = (ConditionType) type;
+                    conditionTypeById.remove(conditionType.getId());
+                    for (Tag tag : conditionType.getTags()) {
+                        conditionTypeByTag.get(tag).remove(conditionType);
+                    }
+                } else if (type instanceof ValueType) {
+                    ValueType valueType = (ValueType) type;
+                    valueTypeById.remove(valueType.getId());
+                    for (Tag tag : valueType.getTags()) {
+                        valueTypeByTag.get(tag).remove(valueType);
+                    }
+                }
+            }
+        }
+    }
+
+
+    public void preDestroy() {
+        bundleContext.removeBundleListener(this);
+    }
+
+    private void loadPredefinedTags(BundleContext bundleContext) {
+        Enumeration<URL> predefinedTagEntries = 
bundleContext.getBundle().findEntries("META-INF/cxs/tags", "*.json", true);
+        if (predefinedTagEntries == null) {
+            return;
+        }
+        while (predefinedTagEntries.hasMoreElements()) {
+            URL predefinedTagURL = predefinedTagEntries.nextElement();
+            logger.debug("Found predefined tags at " + predefinedTagURL + ", 
loading... ");
+
+            try {
+                Tag tag = 
CustomObjectMapper.getObjectMapper().readValue(predefinedTagURL, Tag.class);
+                tag.setPluginId(bundleContext.getBundle().getBundleId());
+                tags.put(tag.getId(), tag);
+            } catch (IOException e) {
+                logger.error("Error while loading segment definition " + 
predefinedTagEntries, e);
+            }
+        }
+
+        // now let's resolve all the children.
+        for (Tag tag : tags.values()) {
+            if (tag.getParentId() != null && tag.getParentId().length() > 0) {
+                Tag parentTag = tags.get(tag.getParentId());
+                if (parentTag != null) {
+                    parentTag.getSubTags().add(tag);
+                }
+            } else {
+                rootTags.add(tag);
+            }
+        }
+    }
+
+    private void loadPredefinedConditionTypes(BundleContext bundleContext) {
+        Enumeration<URL> predefinedConditionEntries = 
bundleContext.getBundle().findEntries("META-INF/cxs/conditions", "*.json", 
true);
+        if (predefinedConditionEntries == null) {
+            return;
+        }
+        ArrayList<PluginType> pluginTypeArrayList = (ArrayList<PluginType>) 
pluginTypes.get(bundleContext.getBundle().getBundleId());
+        while (predefinedConditionEntries.hasMoreElements()) {
+            URL predefinedConditionURL = 
predefinedConditionEntries.nextElement();
+            logger.debug("Found predefined conditions at " + 
predefinedConditionURL + ", loading... ");
+
+            try {
+                ConditionType conditionType = 
CustomObjectMapper.getObjectMapper().readValue(predefinedConditionURL, 
ConditionType.class);
+                
conditionType.setPluginId(bundleContext.getBundle().getBundleId());
+                conditionTypeById.put(conditionType.getId(), conditionType);
+                pluginTypeArrayList.add(conditionType);
+                for (String tagId : conditionType.getTagIDs()) {
+                    Tag tag = tags.get(tagId);
+                    if (tag != null) {
+                        conditionType.getTags().add(tag);
+                        Set<ConditionType> conditionTypes = 
conditionTypeByTag.get(tag);
+                        if (conditionTypes == null) {
+                            conditionTypes = new 
LinkedHashSet<ConditionType>();
+                        }
+                        conditionTypes.add(conditionType);
+                        conditionTypeByTag.put(tag, conditionTypes);
+                    } else {
+                        // we found a tag that is not defined, we will define 
it automatically
+                        logger.warn("Unknown tag " + tagId + " used in 
condition definition " + predefinedConditionURL);
+                    }
+                }
+            } catch (Exception e) {
+                logger.error("Error while loading condition definition " + 
predefinedConditionURL, e);
+            }
+        }
+        for (ConditionType type : conditionTypeById.values()) {
+            if (type.getParentCondition() != null) {
+                ParserHelper.resolveConditionType(this, 
type.getParentCondition());
+            }
+        }
+    }
+
+    private void loadPredefinedActionTypes(BundleContext bundleContext) {
+        Enumeration<URL> predefinedActionsEntries = 
bundleContext.getBundle().findEntries("META-INF/cxs/actions", "*.json", true);
+        if (predefinedActionsEntries == null) {
+            return;
+        }
+        ArrayList<PluginType> pluginTypeArrayList = (ArrayList<PluginType>) 
pluginTypes.get(bundleContext.getBundle().getBundleId());
+        while (predefinedActionsEntries.hasMoreElements()) {
+            URL predefinedActionURL = predefinedActionsEntries.nextElement();
+            logger.debug("Found predefined action at " + predefinedActionURL + 
", loading... ");
+
+            try {
+                ActionType actionType = 
CustomObjectMapper.getObjectMapper().readValue(predefinedActionURL, 
ActionType.class);
+                
actionType.setPluginId(bundleContext.getBundle().getBundleId());
+                actionTypeById.put(actionType.getId(), actionType);
+                pluginTypeArrayList.add(actionType);
+                for (String tagId : actionType.getTagIds()) {
+                    Tag tag = tags.get(tagId);
+                    if (tag != null) {
+                        actionType.getTags().add(tag);
+                        Set<ActionType> actionTypes = actionTypeByTag.get(tag);
+                        if (actionTypes == null) {
+                            actionTypes = new LinkedHashSet<>();
+                        }
+                        actionTypes.add(actionType);
+                        actionTypeByTag.put(tag, actionTypes);
+                    } else {
+                        // we found a tag that is not defined, we will define 
it automatically
+                        logger.warn("Unknown tag " + tagId + " used in action 
definition " + predefinedActionURL);
+                    }
+                }
+            } catch (Exception e) {
+                logger.error("Error while loading action definition " + 
predefinedActionURL, e);
+            }
+        }
+
+    }
+
+    private void loadPredefinedValueTypes(BundleContext bundleContext) {
+        Enumeration<URL> predefinedPropertiesEntries = 
bundleContext.getBundle().findEntries("META-INF/cxs/values", "*.json", true);
+        if (predefinedPropertiesEntries == null) {
+            return;
+        }
+        ArrayList<PluginType> pluginTypeArrayList = (ArrayList<PluginType>) 
pluginTypes.get(bundleContext.getBundle().getBundleId());
+        while (predefinedPropertiesEntries.hasMoreElements()) {
+            URL predefinedPropertyURL = 
predefinedPropertiesEntries.nextElement();
+            logger.debug("Found predefined property type at " + 
predefinedPropertyURL + ", loading... ");
+
+            try {
+                ValueType valueType = 
CustomObjectMapper.getObjectMapper().readValue(predefinedPropertyURL, 
ValueType.class);
+                valueType.setPluginId(bundleContext.getBundle().getBundleId());
+                valueTypeById.put(valueType.getId(), valueType);
+                pluginTypeArrayList.add(valueType);
+                for (String tagId : valueType.getTagIds()) {
+                    Tag tag = tags.get(tagId);
+                    if (tag != null) {
+                        valueType.getTags().add(tag);
+                        Set<ValueType> valueTypes = valueTypeByTag.get(tag);
+                        if (valueTypes == null) {
+                            valueTypes = new LinkedHashSet<ValueType>();
+                        }
+                        valueTypes.add(valueType);
+                        valueTypeByTag.put(tag, valueTypes);
+                    } else {
+                        // we found a tag that is not defined, we will define 
it automatically
+                        logger.warn("Unknown tag " + tagId + " used in 
property type definition " + predefinedPropertyURL);
+                    }
+                }
+            } catch (Exception e) {
+                logger.error("Error while loading property type definition " + 
predefinedPropertyURL, e);
+            }
+        }
+
+    }
+
+    public Set<Tag> getAllTags() {
+        return new HashSet<Tag>(tags.values());
+    }
+
+    public Set<Tag> getRootTags() {
+        return rootTags;
+    }
+
+    public Tag getTag(String tagId) {
+        Tag completeTag = tags.get(tagId);
+        if (completeTag == null) {
+            return null;
+        }
+        return completeTag;
+    }
+
+    public Collection<ConditionType> getAllConditionTypes() {
+        return conditionTypeById.values();
+    }
+
+    public Map<Long, List<PluginType>> getTypesByPlugin() {
+        return pluginTypes;
+    }
+
+    public Set<ConditionType> getConditionTypesByTag(Tag tag, boolean 
includeFromSubtags) {
+        Set<ConditionType> conditionTypes = new LinkedHashSet<ConditionType>();
+        Set<ConditionType> directConditionTypes = conditionTypeByTag.get(tag);
+        if (directConditionTypes != null) {
+            conditionTypes.addAll(directConditionTypes);
+        }
+        if (includeFromSubtags) {
+            for (Tag subTag : tag.getSubTags()) {
+                Set<ConditionType> childConditionTypes = 
getConditionTypesByTag(subTag, true);
+                conditionTypes.addAll(childConditionTypes);
+            }
+        }
+        return conditionTypes;
+    }
+
+    public ConditionType getConditionType(String id) {
+        return conditionTypeById.get(id);
+    }
+
+    public Collection<ActionType> getAllActionTypes() {
+        return actionTypeById.values();
+    }
+
+    public Set<ActionType> getActionTypeByTag(Tag tag, boolean 
includeFromSubtags) {
+        Set<ActionType> actionTypes = new LinkedHashSet<ActionType>();
+        Set<ActionType> directActionTypes = actionTypeByTag.get(tag);
+        if (directActionTypes != null) {
+            actionTypes.addAll(directActionTypes);
+        }
+        if (includeFromSubtags) {
+            for (Tag subTag : tag.getSubTags()) {
+                Set<ActionType> childActionTypes = getActionTypeByTag(subTag, 
true);
+                actionTypes.addAll(childActionTypes);
+            }
+        }
+        return actionTypes;
+    }
+
+    public ActionType getActionType(String id) {
+        return actionTypeById.get(id);
+    }
+
+    public Collection<ValueType> getAllValueTypes() {
+        return valueTypeById.values();
+    }
+
+    public Set<ValueType> getValueTypeByTag(Tag tag, boolean 
includeFromSubtags) {
+        Set<ValueType> valueTypes = new LinkedHashSet<ValueType>();
+        Set<ValueType> directValueTypes = valueTypeByTag.get(tag);
+        if (directValueTypes != null) {
+            valueTypes.addAll(directValueTypes);
+        }
+        if (includeFromSubtags) {
+            for (Tag subTag : tag.getSubTags()) {
+                Set<ValueType> childValueTypes = getValueTypeByTag(subTag, 
true);
+                valueTypes.addAll(childValueTypes);
+            }
+        }
+        return valueTypes;
+    }
+
+    public ValueType getValueType(String id) {
+        return valueTypeById.get(id);
+    }
+
+    public void bundleChanged(BundleEvent event) {
+        switch (event.getType()) {
+            case BundleEvent.STARTED:
+                processBundleStartup(event.getBundle().getBundleContext());
+                break;
+            case BundleEvent.STOPPING:
+                processBundleStop(event.getBundle().getBundleContext());
+                break;
+        }
+    }
+
+    private void loadPredefinedPropertyMergeStrategies(BundleContext 
bundleContext) {
+        Enumeration<URL> predefinedPropertyMergeStrategyEntries = 
bundleContext.getBundle().findEntries("META-INF/cxs/mergers", "*.json", true);
+        if (predefinedPropertyMergeStrategyEntries == null) {
+            return;
+        }
+        ArrayList<PluginType> pluginTypeArrayList = (ArrayList<PluginType>) 
pluginTypes.get(bundleContext.getBundle().getBundleId());
+        while (predefinedPropertyMergeStrategyEntries.hasMoreElements()) {
+            URL predefinedPropertyMergeStrategyURL = 
predefinedPropertyMergeStrategyEntries.nextElement();
+            logger.debug("Found predefined property merge strategy type at " + 
predefinedPropertyMergeStrategyURL + ", loading... ");
+
+            try {
+                PropertyMergeStrategyType propertyMergeStrategyType = 
CustomObjectMapper.getObjectMapper().readValue(predefinedPropertyMergeStrategyURL,
 PropertyMergeStrategyType.class);
+                
propertyMergeStrategyType.setPluginId(bundleContext.getBundle().getBundleId());
+                
propertyMergeStrategyTypeById.put(propertyMergeStrategyType.getId(), 
propertyMergeStrategyType);
+                pluginTypeArrayList.add(propertyMergeStrategyType);
+            } catch (Exception e) {
+                logger.error("Error while loading property type definition " + 
predefinedPropertyMergeStrategyURL, e);
+            }
+        }
+
+    }
+
+    public PropertyMergeStrategyType getPropertyMergeStrategyType(String id) {
+        return propertyMergeStrategyTypeById.get(id);
+    }
+
+    public Set<Condition> extractConditionsByType(Condition rootCondition, 
String typeId) {
+        if (rootCondition.containsParameter("subConditions")) {
+            @SuppressWarnings("unchecked")
+            List<Condition> subConditions = (List<Condition>) 
rootCondition.getParameter("subConditions");
+            Set<Condition> matchingConditions = new HashSet<>();
+            for (Condition condition : subConditions) {
+                matchingConditions.addAll(extractConditionsByType(condition, 
typeId));
+            }
+            return matchingConditions;
+        } else if (rootCondition.getConditionTypeId() != null && 
rootCondition.getConditionTypeId().equals(typeId)) {
+            return Collections.singleton(rootCondition);
+        } else {
+            return Collections.emptySet();
+        }
+    }
+
+    public Condition extractConditionByTag(Condition rootCondition, String 
tagId) {
+        if (rootCondition.containsParameter("subConditions")) {
+            @SuppressWarnings("unchecked")
+            List<Condition> subConditions = (List<Condition>) 
rootCondition.getParameter("subConditions");
+            List<Condition> matchingConditions = new ArrayList<Condition>();
+            for (Condition condition : subConditions) {
+                Condition c = extractConditionByTag(condition, tagId);
+                if (c != null) {
+                    matchingConditions.add(c);
+                }
+            }
+            if (matchingConditions.size() == 0) {
+                return null;
+            } else if (matchingConditions.equals(subConditions)) {
+                return rootCondition;
+            } else if 
(rootCondition.getConditionTypeId().equals("booleanCondition") && 
"and".equals(rootCondition.getParameter("operator"))) {
+                if (matchingConditions.size() == 1) {
+                    return matchingConditions.get(0);
+                } else {
+                    Condition res = new Condition();
+                    res.setConditionType(getConditionType("booleanCondition"));
+                    res.setParameter("operator", "and");
+                    res.setParameter("subConditions", matchingConditions);
+                    return res;
+                }
+            }
+            throw new IllegalArgumentException();
+        } else if (rootCondition.getConditionType() != null && 
rootCondition.getConditionType().getTagIDs().contains(tagId)) {
+            return rootCondition;
+        } else {
+            return null;
+        }
+    }
+
+    @Override
+    public boolean resolveConditionType(Condition rootCondition) {
+        return ParserHelper.resolveConditionType(this, rootCondition);
+    }
+}

http://git-wip-us.apache.org/repos/asf/incubator-unomi/blob/dc1d1520/services/src/main/java/org/apache/unomi/services/services/EventServiceImpl.java
----------------------------------------------------------------------
diff --git 
a/services/src/main/java/org/apache/unomi/services/services/EventServiceImpl.java
 
b/services/src/main/java/org/apache/unomi/services/services/EventServiceImpl.java
new file mode 100644
index 0000000..4b207d9
--- /dev/null
+++ 
b/services/src/main/java/org/apache/unomi/services/services/EventServiceImpl.java
@@ -0,0 +1,223 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements.  See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * The ASF licenses this file to You under the Apache License, Version 2.0
+ * (the "License"); you may not use this file except in compliance with
+ * the License.  You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package org.apache.unomi.services.services;
+
+import org.apache.commons.lang3.StringUtils;
+import org.apache.unomi.api.*;
+import org.apache.unomi.api.actions.ActionPostExecutor;
+import org.apache.unomi.api.conditions.Condition;
+import org.apache.unomi.api.services.DefinitionsService;
+import org.apache.unomi.api.services.EventListenerService;
+import org.apache.unomi.api.services.EventService;
+import org.apache.unomi.api.services.ProfileService;
+import org.apache.unomi.persistence.spi.PersistenceService;
+import org.apache.unomi.persistence.spi.aggregate.TermsAggregate;
+import org.osgi.framework.BundleContext;
+import org.osgi.framework.ServiceReference;
+
+import java.util.*;
+
+public class EventServiceImpl implements EventService {
+
+    private List<EventListenerService> eventListeners = new 
ArrayList<EventListenerService>();
+
+    private PersistenceService persistenceService;
+
+    private ProfileService profileService;
+
+    private DefinitionsService definitionsService;
+
+    private BundleContext bundleContext;
+
+    private Set<String> predefinedEventTypeIds = new LinkedHashSet<String>();
+
+    public void setPredefinedEventTypeIds(Set<String> predefinedEventTypeIds) {
+        this.predefinedEventTypeIds = predefinedEventTypeIds;
+    }
+
+    public void setPersistenceService(PersistenceService persistenceService) {
+        this.persistenceService = persistenceService;
+    }
+
+    public void setProfileService(ProfileService profileService) {
+        this.profileService = profileService;
+    }
+
+    public void setDefinitionsService(DefinitionsService definitionsService) {
+        this.definitionsService = definitionsService;
+    }
+
+    public void setBundleContext(BundleContext bundleContext) {
+        this.bundleContext = bundleContext;
+    }
+
+    public int send(Event event) {
+        if (event.isPersistent()) {
+            persistenceService.save(event);
+        }
+
+        int changes = NO_CHANGE;
+
+        Profile profile = event.getProfile();
+        final Session session = event.getSession();
+        if (event.isPersistent() && session != null) {
+            session.setLastEventDate(event.getTimeStamp());
+        }
+
+        if (profile != null) {
+            for (EventListenerService eventListenerService : eventListeners) {
+                if (eventListenerService.canHandle(event)) {
+                    changes |= eventListenerService.onEvent(event);
+                }
+            }
+            // At the end of the processing event execute the post executor 
actions
+            for (ActionPostExecutor actionPostExecutor : 
event.getActionPostExecutors()) {
+                changes |= actionPostExecutor.execute() ? changes : NO_CHANGE;
+            }
+
+            if ((changes & PROFILE_UPDATED) == PROFILE_UPDATED) {
+                Event profileUpdated = new Event("profileUpdated", session, 
profile, event.getScope(), event.getSource(), profile, event.getTimeStamp());
+                profileUpdated.setPersistent(false);
+                profileUpdated.getAttributes().putAll(event.getAttributes());
+                changes |= send(profileUpdated);
+                if (session != null) {
+                    changes |= SESSION_UPDATED;
+                    session.setProfile(profile);
+                }
+            }
+        }
+        return changes;
+    }
+
+    @Override
+    public List<EventProperty> getEventProperties() {
+        Map<String, Map<String, Object>> mappings = 
persistenceService.getMapping(Event.ITEM_TYPE);
+        List<EventProperty> props = new ArrayList<>(mappings.size());
+        getEventProperties(mappings, props, "");
+        return props;
+    }
+
+    @SuppressWarnings("unchecked")
+    private void getEventProperties(Map<String, Map<String, Object>> mappings, 
List<EventProperty> props, String prefix) {
+        for (Map.Entry<String, Map<String, Object>> e : mappings.entrySet()) {
+            if (e.getValue().get("properties") != null) {
+                getEventProperties((Map<String, Map<String, Object>>) 
e.getValue().get("properties"), props, prefix + e.getKey() + ".");
+            } else {
+                props.add(new EventProperty(prefix + e.getKey(), (String) 
e.getValue().get("type")));
+            }
+        }
+    }
+
+    public Set<String> getEventTypeIds() {
+        Map<String, Long> dynamicEventTypeIds = 
persistenceService.aggregateQuery(null, new TermsAggregate("eventType"), 
Event.ITEM_TYPE);
+        Set<String> eventTypeIds = new 
LinkedHashSet<String>(predefinedEventTypeIds);
+        eventTypeIds.addAll(dynamicEventTypeIds.keySet());
+        return eventTypeIds;
+    }
+
+    @Override
+    public PartialList<Event> searchEvents(Condition condition, int offset, 
int size) {
+        return persistenceService.query(condition, "timeStamp", Event.class, 
offset, size);
+    }
+
+    @Override
+    public PartialList<Event> searchEvents(String sessionId, String[] 
eventTypes, String query, int offset, int size, String sortBy) {
+        List<Condition> conditions = new ArrayList<Condition>();
+
+        Condition condition = new 
Condition(definitionsService.getConditionType("eventPropertyCondition"));
+        condition.setParameter("propertyName", "sessionId");
+        condition.setParameter("propertyValue", sessionId);
+        condition.setParameter("comparisonOperator", "equals");
+        conditions.add(condition);
+
+        condition = new 
Condition(definitionsService.getConditionType("booleanCondition"));
+        condition.setParameter("operator", "or");
+        List<Condition> subConditions = new ArrayList<Condition>();
+        for (String eventType : eventTypes) {
+            Condition subCondition = new 
Condition(definitionsService.getConditionType("eventPropertyCondition"));
+            subCondition.setParameter("propertyName", "eventType");
+            subCondition.setParameter("propertyValue", eventType);
+            subCondition.setParameter("comparisonOperator", "equals");
+            subConditions.add(subCondition);
+        }
+        condition.setParameter("subConditions", subConditions);
+        conditions.add(condition);
+
+        condition = new 
Condition(definitionsService.getConditionType("booleanCondition"));
+        condition.setParameter("operator", "and");
+        condition.setParameter("subConditions", conditions);
+
+        if (StringUtils.isNotBlank(query)) {
+            return persistenceService.queryFullText(query, condition, sortBy, 
Event.class, offset, size);
+        } else {
+            return persistenceService.query(condition, sortBy, Event.class, 
offset, size);
+        }
+    }
+
+    public boolean hasEventAlreadyBeenRaised(Event event, boolean session) {
+        List<Condition> conditions = new ArrayList<Condition>();
+
+        Condition profileIdCondition = new 
Condition(definitionsService.getConditionType("eventPropertyCondition"));
+        if (session) {
+            profileIdCondition.setParameter("propertyName", "sessionId");
+            profileIdCondition.setParameter("propertyValue", 
event.getSessionId());
+        } else {
+            profileIdCondition.setParameter("propertyName", "profileId");
+            profileIdCondition.setParameter("propertyValue", 
event.getProfileId());
+        }
+        profileIdCondition.setParameter("comparisonOperator", "equals");
+        conditions.add(profileIdCondition);
+
+        Condition condition = new 
Condition(definitionsService.getConditionType("eventPropertyCondition"));
+        condition.setParameter("propertyName", "eventType");
+        condition.setParameter("propertyValue", event.getEventType());
+        condition.setParameter("comparisonOperator", "equals");
+        conditions.add(condition);
+
+        condition = new 
Condition(definitionsService.getConditionType("eventPropertyCondition"));
+        condition.setParameter("propertyName", "target.itemId");
+        condition.setParameter("propertyValue", event.getTarget().getItemId());
+        condition.setParameter("comparisonOperator", "equals");
+        conditions.add(condition);
+
+        condition = new 
Condition(definitionsService.getConditionType("eventPropertyCondition"));
+        condition.setParameter("propertyName", "target.itemType");
+        condition.setParameter("propertyValue", 
event.getTarget().getItemType());
+        condition.setParameter("comparisonOperator", "equals");
+        conditions.add(condition);
+
+        Condition andCondition = new 
Condition(definitionsService.getConditionType("booleanCondition"));
+        andCondition.setParameter("operator", "and");
+        andCondition.setParameter("subConditions", conditions);
+        long size = persistenceService.queryCount(andCondition, 
Event.ITEM_TYPE);
+        return size > 0;
+    }
+
+
+    public void bind(ServiceReference<EventListenerService> serviceReference) {
+        EventListenerService eventListenerService = 
bundleContext.getService(serviceReference);
+        eventListeners.add(eventListenerService);
+    }
+
+    public void unbind(ServiceReference<EventListenerService> 
serviceReference) {
+        if (serviceReference != null) {
+            EventListenerService eventListenerService = 
bundleContext.getService(serviceReference);
+            eventListeners.remove(eventListenerService);
+        }
+    }
+}

Reply via email to