http://git-wip-us.apache.org/repos/asf/incubator-unomi/blob/dc1d1520/services/src/main/java/org/apache/unomi/services/services/GoalsServiceImpl.java
----------------------------------------------------------------------
diff --git 
a/services/src/main/java/org/apache/unomi/services/services/GoalsServiceImpl.java
 
b/services/src/main/java/org/apache/unomi/services/services/GoalsServiceImpl.java
new file mode 100644
index 0000000..d280f9a
--- /dev/null
+++ 
b/services/src/main/java/org/apache/unomi/services/services/GoalsServiceImpl.java
@@ -0,0 +1,613 @@
+/*
+ * 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.*;
+import org.apache.unomi.api.actions.Action;
+import org.apache.unomi.api.campaigns.Campaign;
+import org.apache.unomi.api.campaigns.CampaignDetail;
+import org.apache.unomi.api.campaigns.events.CampaignEvent;
+import org.apache.unomi.api.conditions.Condition;
+import org.apache.unomi.api.conditions.ConditionType;
+import org.apache.unomi.api.goals.Goal;
+import org.apache.unomi.api.goals.GoalReport;
+import org.apache.unomi.api.query.AggregateQuery;
+import org.apache.unomi.api.query.Query;
+import org.apache.unomi.api.rules.Rule;
+import org.apache.unomi.api.services.DefinitionsService;
+import org.apache.unomi.api.services.GoalsService;
+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.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 GoalsServiceImpl implements GoalsService, 
SynchronousBundleListener {
+    private static final Logger logger = 
LoggerFactory.getLogger(RulesServiceImpl.class.getName());
+
+    private BundleContext bundleContext;
+
+    private PersistenceService persistenceService;
+
+    private DefinitionsService definitionsService;
+
+    private RulesService rulesService;
+
+    private Map<Tag, Set<Goal>> goalByTag = new HashMap<>();
+
+    public void setBundleContext(BundleContext bundleContext) {
+        this.bundleContext = bundleContext;
+    }
+
+    public void setPersistenceService(PersistenceService persistenceService) {
+        this.persistenceService = persistenceService;
+    }
+
+    public void setDefinitionsService(DefinitionsService definitionsService) {
+        this.definitionsService = definitionsService;
+    }
+
+    public void setRulesService(RulesService rulesService) {
+        this.rulesService = rulesService;
+    }
+
+    public void postConstruct() {
+        logger.debug("postConstruct {" + bundleContext.getBundle() + "}");
+
+        loadPredefinedGoals(bundleContext);
+        loadPredefinedCampaigns(bundleContext);
+        for (Bundle bundle : bundleContext.getBundles()) {
+            if (bundle.getBundleContext() != null) {
+                loadPredefinedGoals(bundle.getBundleContext());
+                loadPredefinedCampaigns(bundle.getBundleContext());
+            }
+        }
+        bundleContext.addBundleListener(this);
+    }
+
+    public void preDestroy() {
+        bundleContext.removeBundleListener(this);
+    }
+
+    private void processBundleStartup(BundleContext bundleContext) {
+        if (bundleContext == null) {
+            return;
+        }
+        loadPredefinedGoals(bundleContext);
+        loadPredefinedCampaigns(bundleContext);
+    }
+
+    private void processBundleStop(BundleContext bundleContext) {
+        if (bundleContext == null) {
+            return;
+        }
+        List<PluginType> types = 
definitionsService.getTypesByPlugin().get(bundleContext.getBundle().getBundleId());
+        List<String> removedConditions = new ArrayList<String>();
+        if (types != null) {
+            for (PluginType type : types) {
+                if (type instanceof ConditionType) {
+                    removedConditions.add(((ConditionType) type).getId());
+                }
+            }
+        }
+        if (!removedConditions.isEmpty()) {
+            for (Goal goal : persistenceService.getAllItems(Goal.class)) {
+                List<String> conditions = 
ParserHelper.getConditionTypeIds(goal.getTargetEvent());
+                if (goal.getStartEvent() != null) {
+                    
conditions.addAll(ParserHelper.getConditionTypeIds(goal.getStartEvent()));
+                }
+
+                if (!Collections.disjoint(conditions, removedConditions)) {
+                    logger.info("Disable goal " + goal.getItemId());
+                    goal.getMetadata().setEnabled(false);
+                    setGoal(goal);
+                }
+            }
+        }
+    }
+
+    private void loadPredefinedGoals(BundleContext bundleContext) {
+        Enumeration<URL> predefinedRuleEntries = 
bundleContext.getBundle().findEntries("META-INF/cxs/goals", "*.json", true);
+        if (predefinedRuleEntries == null) {
+            return;
+        }
+        while (predefinedRuleEntries.hasMoreElements()) {
+            URL predefinedGoalURL = predefinedRuleEntries.nextElement();
+            logger.debug("Found predefined goals at " + predefinedGoalURL + ", 
loading... ");
+
+            try {
+                Goal goal = 
CustomObjectMapper.getObjectMapper().readValue(predefinedGoalURL, Goal.class);
+                if (goal.getMetadata().getScope() == null) {
+                    goal.getMetadata().setScope("systemscope");
+                }
+                if (getGoal(goal.getMetadata().getId()) == null) {
+                    for (String tagId : goal.getMetadata().getTags()) {
+                        Tag tag = definitionsService.getTag(tagId);
+                        if (tag != null) {
+                            Set<Goal> goals = goalByTag.get(tag);
+                            if (goals == null) {
+                                goals = new LinkedHashSet<>();
+                            }
+                            goals.add(goal);
+                            goalByTag.put(tag, goals);
+                        } else {
+                            // we found a tag that is not defined, we will 
define it automatically
+                            logger.warn("Unknown tag " + tagId + " used in 
goal definition " + predefinedGoalURL);
+                        }
+                    }
+
+                    setGoal(goal);
+                }
+            } catch (IOException e) {
+                logger.error("Error while loading segment definition " + 
predefinedGoalURL, e);
+            }
+        }
+    }
+
+    private void createRule(Goal goal, Condition event, String id, boolean 
testStart) {
+        Rule rule = new Rule(new Metadata(goal.getMetadata().getScope(), 
goal.getMetadata().getId() + id + "Event", "Auto generated rule for goal " + 
goal.getMetadata().getName(), ""));
+        Condition res = new Condition();
+        List<Condition> subConditions = new ArrayList<Condition>();
+        
res.setConditionType(definitionsService.getConditionType("booleanCondition"));
+        res.setParameter("operator", "and");
+        res.setParameter("subConditions", subConditions);
+
+        subConditions.add(event);
+
+        Condition notExist = new Condition();
+        
notExist.setConditionType(definitionsService.getConditionType("sessionPropertyCondition"));
+        notExist.setParameter("propertyName", "systemProperties.goals." + 
goal.getMetadata().getId() + id + "Reached");
+        notExist.setParameter("comparisonOperator", "missing");
+        subConditions.add(notExist);
+
+        if (testStart) {
+            Condition startExists = new Condition();
+            
startExists.setConditionType(definitionsService.getConditionType("sessionPropertyCondition"));
+            startExists.setParameter("propertyName", "systemProperties.goals." 
+ goal.getMetadata().getId() + "StartReached");
+            startExists.setParameter("comparisonOperator", "exists");
+            subConditions.add(startExists);
+        }
+
+        if (goal.getCampaignId() != null) {
+            Condition engagedInCampaign = new Condition();
+            
engagedInCampaign.setConditionType(definitionsService.getConditionType("sessionPropertyCondition"));
+            engagedInCampaign.setParameter("propertyName", 
"systemProperties.campaigns." + goal.getCampaignId() + "Engaged");
+            engagedInCampaign.setParameter("comparisonOperator", "exists");
+            subConditions.add(engagedInCampaign);
+        }
+
+        rule.setCondition(res);
+        rule.getMetadata().setHidden(true);
+        Action action1 = new Action();
+        
action1.setActionType(definitionsService.getActionType("setPropertyAction"));
+        String name = "systemProperties.goals." + goal.getMetadata().getId() + 
id + "Reached";
+        action1.setParameter("setPropertyName", name);
+        action1.setParameter("setPropertyValue", "now");
+        action1.setParameter("storeInSession", true);
+        Action action2 = new Action();
+        
action2.setActionType(definitionsService.getActionType("setPropertyAction"));
+        action2.setParameter("setPropertyName", name);
+        action2.setParameter("setPropertyValue", 
"script::profile.properties.?"+name+" != null ? (profile.properties."+name+") : 
'now'");
+        action2.setParameter("storeInSession", false);
+        rule.setActions(Arrays.asList(action1, action2));
+
+        if (id.equals("Target")) {
+            Action action3 = new Action();
+            
action3.setActionType(definitionsService.getActionType("sendEventAction"));
+            action3.setParameter("eventType", "goal");
+            action3.setParameter("eventTarget", goal);
+            action3.setParameter("eventProperties", new HashMap<String, 
Object>());
+            rule.setActions(Arrays.asList(action1,action2,action3));
+        }
+
+        rulesService.setRule(rule);
+    }
+
+    public Set<Metadata> getGoalMetadatas() {
+        Set<Metadata> descriptions = new HashSet<Metadata>();
+        for (Goal definition : persistenceService.getAllItems(Goal.class, 0, 
50, null).getList()) {
+            descriptions.add(definition.getMetadata());
+        }
+        return descriptions;
+    }
+
+    public Set<Metadata> getGoalMetadatas(Query query) {
+        definitionsService.resolveConditionType(query.getCondition());
+        Set<Metadata> descriptions = new HashSet<Metadata>();
+        for (Goal definition : persistenceService.query(query.getCondition(), 
query.getSortby(), Goal.class, query.getOffset(), query.getLimit()).getList()) {
+            descriptions.add(definition.getMetadata());
+        }
+        return descriptions;
+    }
+
+
+    public Goal getGoal(String goalId) {
+        Goal goal = persistenceService.load(goalId, Goal.class);
+        if (goal != null) {
+            ParserHelper.resolveConditionType(definitionsService, 
goal.getStartEvent());
+            ParserHelper.resolveConditionType(definitionsService, 
goal.getTargetEvent());
+        }
+        return goal;
+    }
+
+    @Override
+    public void removeGoal(String goalId) {
+        persistenceService.remove(goalId, Goal.class);
+        rulesService.removeRule(goalId + "StartEvent");
+        rulesService.removeRule(goalId + "TargetEvent");
+    }
+
+    @Override
+    public void setGoal(Goal goal) {
+        ParserHelper.resolveConditionType(definitionsService, 
goal.getStartEvent());
+        ParserHelper.resolveConditionType(definitionsService, 
goal.getTargetEvent());
+
+        if (goal.getMetadata().isEnabled()) {
+            if (goal.getStartEvent() != null) {
+                createRule(goal, goal.getStartEvent(), "Start", false);
+            }
+            if (goal.getTargetEvent() != null) {
+                createRule(goal, goal.getTargetEvent(), "Target", 
goal.getStartEvent() != null);
+            }
+        } else {
+            rulesService.removeRule(goal.getMetadata().getId() + "StartEvent");
+            rulesService.removeRule(goal.getMetadata().getId() + 
"TargetEvent");
+        }
+
+        persistenceService.save(goal);
+    }
+
+    public Set<Metadata> getCampaignGoalMetadatas(String campaignId) {
+        Set<Metadata> descriptions = new HashSet<Metadata>();
+        for (Goal definition : persistenceService.query("campaignId", 
campaignId, null, Goal.class,0,50).getList()) {
+            descriptions.add(definition.getMetadata());
+        }
+        return descriptions;
+    }
+
+    private void loadPredefinedCampaigns(BundleContext bundleContext) {
+        Enumeration<URL> predefinedRuleEntries = 
bundleContext.getBundle().findEntries("META-INF/cxs/campaigns", "*.json", true);
+        if (predefinedRuleEntries == null) {
+            return;
+        }
+        while (predefinedRuleEntries.hasMoreElements()) {
+            URL predefinedCampaignURL = predefinedRuleEntries.nextElement();
+            logger.debug("Found predefined campaigns at " + 
predefinedCampaignURL + ", loading... ");
+
+            try {
+                Campaign campaign = 
CustomObjectMapper.getObjectMapper().readValue(predefinedCampaignURL, 
Campaign.class);
+                if (getCampaign(campaign.getMetadata().getId()) == null) {
+                    setCampaign(campaign);
+                }
+            } catch (IOException e) {
+                logger.error("Error while loading segment definition " + 
predefinedCampaignURL, e);
+            }
+        }
+    }
+
+    private void createRule(Campaign campaign, Condition event) {
+        Rule rule = new Rule(new Metadata(campaign.getMetadata().getScope(), 
campaign.getMetadata().getId() + "EntryEvent", "Auto generated rule for 
campaign " + campaign.getMetadata().getName(), ""));
+        Condition res = new Condition();
+        List<Condition> subConditions = new ArrayList<Condition>();
+        
res.setConditionType(definitionsService.getConditionType("booleanCondition"));
+        res.setParameter("operator", "and");
+        res.setParameter("subConditions", subConditions);
+
+        if (campaign.getStartDate() != null) {
+            Condition startCondition = new Condition();
+            
startCondition.setConditionType(definitionsService.getConditionType("sessionPropertyCondition"));
+            startCondition.setParameter("propertyName", "timeStamp");
+            startCondition.setParameter("comparisonOperator", "greaterThan");
+            startCondition.setParameter("propertyValueDate", 
campaign.getStartDate());
+            subConditions.add(startCondition);
+        }
+
+        if (campaign.getEndDate() != null) {
+            Condition endCondition = new Condition();
+            
endCondition.setConditionType(definitionsService.getConditionType("sessionPropertyCondition"));
+            endCondition.setParameter("propertyName", "timeStamp");
+            endCondition.setParameter("comparisonOperator", "lessThan");
+            endCondition.setParameter("propertyValueDate", 
campaign.getEndDate());
+            subConditions.add(endCondition);
+        }
+
+        rule.setPriority(-5);
+
+        subConditions.add(event);
+
+        rule.setCondition(res);
+        rule.getMetadata().setHidden(true);
+        Action action1 = new Action();
+        
action1.setActionType(definitionsService.getActionType("setPropertyAction"));
+        String name = "systemProperties.campaigns." + 
campaign.getMetadata().getId() + "Engaged";
+        action1.setParameter("setPropertyName", name);
+        action1.setParameter("setPropertyValue", "now");
+        action1.setParameter("storeInSession", true);
+        Action action2 = new Action();
+        
action2.setActionType(definitionsService.getActionType("setPropertyAction"));
+        action2.setParameter("setPropertyName", name);
+        action2.setParameter("setPropertyValue", 
"script::profile.properties.?"+name+" != null ? (profile.properties."+name+") : 
'now'");
+        action2.setParameter("storeInSession", false);
+        rule.setActions(Arrays.asList(action1,action2));
+        rulesService.setRule(rule);
+    }
+
+
+    public Set<Metadata> getCampaignMetadatas() {
+        Set<Metadata> descriptions = new HashSet<Metadata>();
+        for (Campaign definition : 
persistenceService.getAllItems(Campaign.class, 0, 50, null).getList()) {
+            descriptions.add(definition.getMetadata());
+        }
+        return descriptions;
+    }
+
+    public Set<Metadata> getCampaignMetadatas(Query query) {
+        definitionsService.resolveConditionType(query.getCondition());
+        Set<Metadata> descriptions = new HashSet<Metadata>();
+        for (Campaign definition : 
persistenceService.query(query.getCondition(), query.getSortby(), 
Campaign.class, query.getOffset(), query.getLimit()).getList()) {
+            descriptions.add(definition.getMetadata());
+        }
+        return descriptions;
+    }
+
+    public PartialList<CampaignDetail> getCampaignDetails(Query query) {
+        definitionsService.resolveConditionType(query.getCondition());
+        PartialList<Campaign> campaigns = 
persistenceService.query(query.getCondition(), query.getSortby(), 
Campaign.class, query.getOffset(), query.getLimit());
+        List<CampaignDetail> details = new LinkedList<>();
+        for (Campaign definition : campaigns.getList()) {
+            final CampaignDetail campaignDetail = 
getCampaignDetail(definition);
+            if (campaignDetail != null) {
+                details.add(campaignDetail);
+            }
+        }
+        return new PartialList<>(details, campaigns.getOffset(), 
campaigns.getPageSize(), campaigns.getTotalSize());
+    }
+
+    public CampaignDetail getCampaignDetail(String id) {
+        return getCampaignDetail(getCampaign(id));
+    }
+
+    private CampaignDetail getCampaignDetail(Campaign campaign) {
+        if (campaign == null) {
+            return null;
+        }
+
+        CampaignDetail campaignDetail = new CampaignDetail(campaign);
+
+        // engaged profile
+        Condition profileEngagedCondition = new 
Condition(definitionsService.getConditionType("profilePropertyCondition"));
+        profileEngagedCondition.setParameter("propertyName", 
"systemProperties.campaigns." + campaign.getMetadata().getId() + "Engaged");
+        profileEngagedCondition.setParameter("comparisonOperator", "exists");
+        
campaignDetail.setEngagedProfiles(persistenceService.queryCount(profileEngagedCondition,
 Profile.ITEM_TYPE));
+
+        // number of goals
+        Condition campaignGoalsCondition = new 
Condition(definitionsService.getConditionType("sessionPropertyCondition"));
+        campaignGoalsCondition.setParameter("propertyName", "campaignId");
+        campaignGoalsCondition.setParameter("comparisonOperator", "equals");
+        campaignGoalsCondition.setParameter("propertyValue", 
campaign.getMetadata().getId());
+        
campaignDetail.setNumberOfGoals(persistenceService.queryCount(campaignGoalsCondition,
 Goal.ITEM_TYPE));
+
+        // sessions
+        Condition sessionEngagedCondition = new 
Condition(definitionsService.getConditionType("sessionPropertyCondition"));
+        sessionEngagedCondition.setParameter("propertyName", 
"systemProperties.campaigns." + campaign.getMetadata().getId() + "Engaged");
+        sessionEngagedCondition.setParameter("comparisonOperator", "exists");
+        
campaignDetail.setCampaignSessionViews(persistenceService.queryCount(sessionEngagedCondition,
 Session.ITEM_TYPE));
+
+        // sessions
+        Condition sessionConvertedCondition = new 
Condition(definitionsService.getConditionType("sessionPropertyCondition"));
+        sessionConvertedCondition.setParameter("propertyName", 
"systemProperties.goals." + campaign.getPrimaryGoal() + "TargetReached");
+        sessionConvertedCondition.setParameter("comparisonOperator", "exists");
+        
campaignDetail.setCampaignSessionSuccess(persistenceService.queryCount(sessionConvertedCondition,
 Session.ITEM_TYPE));
+
+        // conversion
+        campaignDetail.setConversionRate((double) 
campaignDetail.getCampaignSessionSuccess() / 
(campaignDetail.getCampaignSessionViews() > 0  ? (double) 
campaignDetail.getCampaignSessionViews() : 1));
+        return campaignDetail;
+    }
+
+    public Campaign getCampaign(String id) {
+        Campaign campaign = persistenceService.load(id, Campaign.class);
+        if (campaign != null) {
+            ParserHelper.resolveConditionType(definitionsService, 
campaign.getEntryCondition());
+        }
+        return campaign;
+    }
+
+    public void removeCampaign(String id) {
+        for(Metadata m : getCampaignGoalMetadatas(id)) {
+            removeGoal(m.getId());
+        }
+        rulesService.removeRule(id + "EntryEvent");
+        persistenceService.remove(id, Campaign.class);
+    }
+
+    public void setCampaign(Campaign campaign) {
+        ParserHelper.resolveConditionType(definitionsService, 
campaign.getEntryCondition());
+
+        if(rulesService.getRule(campaign.getMetadata().getId() + "EntryEvent") 
!= null) {
+            rulesService.removeRule(campaign.getMetadata().getId() + 
"EntryEvent");
+        }
+
+        if (campaign.getMetadata().isEnabled()) {
+            if (campaign.getEntryCondition() != null) {
+                createRule(campaign, campaign.getEntryCondition());
+            }
+        }
+
+        persistenceService.save(campaign);
+    }
+
+    public GoalReport getGoalReport(String goalId) {
+        return getGoalReport(goalId, null);
+    }
+
+    public GoalReport getGoalReport(String goalId, AggregateQuery query) {
+        Condition condition = new 
Condition(definitionsService.getConditionType("booleanCondition"));
+        final ArrayList<Condition> list = new ArrayList<Condition>();
+        condition.setParameter("operator", "and");
+        condition.setParameter("subConditions", list);
+
+        Goal g = getGoal(goalId);
+
+        Condition goalTargetCondition = new 
Condition(definitionsService.getConditionType("sessionPropertyCondition"));
+        goalTargetCondition.setParameter("propertyName",  
"systemProperties.goals." + goalId+ "TargetReached");
+        goalTargetCondition.setParameter("comparisonOperator", "exists");
+
+        Condition goalStartCondition;
+        if (g.getStartEvent() == null && g.getCampaignId() != null) {
+            goalStartCondition = new 
Condition(definitionsService.getConditionType("sessionPropertyCondition"));
+            goalStartCondition.setParameter("propertyName", 
"systemProperties.campaigns." + g.getCampaignId() + "Engaged");
+            goalStartCondition.setParameter("comparisonOperator", "exists");
+        } else if (g.getStartEvent() == null) {
+            goalStartCondition = new 
Condition(definitionsService.getConditionType("matchAllCondition"));
+        } else {
+            goalStartCondition = new 
Condition(definitionsService.getConditionType("sessionPropertyCondition"));
+            goalStartCondition.setParameter("propertyName", 
"systemProperties.goals." + goalId + "StartReached");
+            goalStartCondition.setParameter("comparisonOperator", "exists");
+        }
+
+        if (query != null && query.getCondition() != null) {
+            ParserHelper.resolveConditionType(definitionsService, 
query.getCondition());
+            list.add(query.getCondition());
+        }
+
+        Map<String, Long> all;
+        Map<String, Long> match;
+
+        // resolve aggregate
+        BaseAggregate aggregate = null;
+        if(query != null && query.getAggregate() != null && 
query.getAggregate().getProperty() != null) {
+            if (query.getAggregate().getType() != null){
+                // try to guess the aggregate type
+                if(query.getAggregate().getType().equals("date")) {
+                    String interval = (String) 
query.getAggregate().getParameters().get("interval");
+                    String format = (String) 
query.getAggregate().getParameters().get("format");
+                    aggregate = new 
DateAggregate(query.getAggregate().getProperty(), interval, format);
+                } else if (query.getAggregate().getType().equals("dateRange") 
&& query.getAggregate().getDateRanges() != null && 
query.getAggregate().getDateRanges().size() > 0) {
+                    String format = (String) 
query.getAggregate().getParameters().get("format");
+                    aggregate = new 
DateRangeAggregate(query.getAggregate().getProperty(), format, 
query.getAggregate().getDateRanges());
+                } else if 
(query.getAggregate().getType().equals("numericRange") && 
query.getAggregate().getNumericRanges() != null && 
query.getAggregate().getNumericRanges().size() > 0) {
+                    aggregate = new 
NumericRangeAggregate(query.getAggregate().getProperty(), 
query.getAggregate().getNumericRanges());
+                } else if (query.getAggregate().getType().equals("ipRange") && 
query.getAggregate().ipRanges() != null && 
query.getAggregate().ipRanges().size() > 0) {
+                    aggregate = new 
IpRangeAggregate(query.getAggregate().getProperty(), 
query.getAggregate().ipRanges());
+                }
+            }
+
+            if(aggregate == null){
+                aggregate = new 
TermsAggregate(query.getAggregate().getProperty());
+            }
+        }
+
+        if (aggregate != null) {
+            list.add(goalStartCondition);
+            all = persistenceService.aggregateQuery(condition, aggregate, 
Session.ITEM_TYPE);
+
+            list.remove(goalStartCondition);
+            list.add(goalTargetCondition);
+            match = persistenceService.aggregateQuery(condition, aggregate, 
Session.ITEM_TYPE);
+        } else {
+            list.add(goalStartCondition);
+            all = new HashMap<String, Long>();
+            all.put("_filtered", persistenceService.queryCount(condition, 
Session.ITEM_TYPE));
+
+            list.remove(goalStartCondition);
+            list.add(goalTargetCondition);
+            match = new HashMap<String, Long>();
+            match.put("_filtered", persistenceService.queryCount(condition, 
Session.ITEM_TYPE));
+        }
+
+        GoalReport report = new GoalReport();
+
+        GoalReport.Stat stat = new GoalReport.Stat();
+        Long allFiltered = all.remove("_filtered");
+        Long matchFiltered = match.remove("_filtered");
+        stat.setStartCount(allFiltered != null ? allFiltered : 0);
+        stat.setTargetCount(matchFiltered != null ? matchFiltered : 0);
+        stat.setConversionRate(stat.getStartCount() > 0 ? (float) 
stat.getTargetCount() / (float) stat.getStartCount() : 0);
+        report.setGlobalStats(stat);
+        all.remove("_all");
+        report.setSplit(new LinkedList<GoalReport.Stat>());
+        for (Map.Entry<String, Long> entry : all.entrySet()) {
+            GoalReport.Stat dateStat = new GoalReport.Stat();
+            dateStat.setKey(entry.getKey());
+            dateStat.setStartCount(entry.getValue());
+            dateStat.setTargetCount(match.containsKey(entry.getKey()) ? 
match.get(entry.getKey()) : 0);
+            dateStat.setConversionRate(dateStat.getStartCount() > 0 ? (float) 
dateStat.getTargetCount() / (float) dateStat.getStartCount() : 0);
+            dateStat.setPercentage(stat.getTargetCount() > 0 ? (float) 
dateStat.getTargetCount() / (float) stat.getTargetCount() : 0);
+            report.getSplit().add(dateStat);
+        }
+
+        return report;
+    }
+
+    public Set<Goal> getGoalByTag(Tag tag, boolean recursive) {
+        Set<Goal> goals = new LinkedHashSet<>();
+        Set<Goal> directGoals = goalByTag.get(tag);
+        if (directGoals != null) {
+            goals.addAll(directGoals);
+        }
+        if (recursive) {
+            for (Tag subTag : tag.getSubTags()) {
+                Set<Goal> childGoals = getGoalByTag(subTag, true);
+                goals.addAll(childGoals);
+            }
+        }
+        return goals;
+    }
+
+    // Campaign Event management methods
+    @Override
+    public PartialList<CampaignEvent> getEvents(Query query) {
+        if(query.isForceRefresh()){
+            persistenceService.refresh();
+        }
+        definitionsService.resolveConditionType(query.getCondition());
+        return persistenceService.query(query.getCondition(), 
query.getSortby(), CampaignEvent.class, query.getOffset(), query.getLimit());
+    }
+
+    @Override
+    public void setCampaignEvent(CampaignEvent event) {
+        persistenceService.save(event);
+    }
+
+    @Override
+    public void removeCampaignEvent(String campaignEventId) {
+        persistenceService.remove(campaignEventId, CampaignEvent.class);
+    }
+
+    public void bundleChanged(BundleEvent event) {
+        switch (event.getType()) {
+            case BundleEvent.STARTED:
+                processBundleStartup(event.getBundle().getBundleContext());
+                break;
+            case BundleEvent.STOPPING:
+                processBundleStop(event.getBundle().getBundleContext());
+                break;
+        }
+    }
+
+}

http://git-wip-us.apache.org/repos/asf/incubator-unomi/blob/dc1d1520/services/src/main/java/org/apache/unomi/services/services/ParserHelper.java
----------------------------------------------------------------------
diff --git 
a/services/src/main/java/org/apache/unomi/services/services/ParserHelper.java 
b/services/src/main/java/org/apache/unomi/services/services/ParserHelper.java
new file mode 100644
index 0000000..d16a0d3
--- /dev/null
+++ 
b/services/src/main/java/org/apache/unomi/services/services/ParserHelper.java
@@ -0,0 +1,126 @@
+/*
+ * 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.PropertyType;
+import org.apache.unomi.api.ValueType;
+import org.apache.unomi.api.actions.Action;
+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.slf4j.Logger;
+import org.slf4j.LoggerFactory;
+
+import java.util.ArrayList;
+import java.util.Collection;
+import java.util.List;
+
+public class ParserHelper {
+
+    private static final Logger logger = 
LoggerFactory.getLogger(ParserHelper.class);
+
+    public static boolean resolveConditionType(final DefinitionsService 
definitionsService, Condition rootCondition) {
+        if (rootCondition == null) {
+            return false;
+        }
+        final List<String> result = new ArrayList<String>();
+        visitConditions(rootCondition, new ConditionVisitor() {
+            @Override
+            public void visit(Condition condition) {
+                if (condition.getConditionType() == null) {
+                    ConditionType conditionType = 
definitionsService.getConditionType(condition.getConditionTypeId());
+                    if (conditionType != null) {
+                        condition.setConditionType(conditionType);
+                    } else {
+                        result.add(condition.getConditionTypeId());
+                    }
+                }
+            }
+        });
+        if (!result.isEmpty()) {
+            logger.warn("Couldn't resolve condition types : " + result);
+        }
+        return result.isEmpty();
+    }
+
+    public static List<String> getConditionTypeIds(Condition rootCondition) {
+        final List<String> result = new ArrayList<String>();
+        visitConditions(rootCondition, new ConditionVisitor() {
+            @Override
+            public void visit(Condition condition) {
+                result.add(condition.getConditionTypeId());
+            }
+        });
+        return result;
+    }
+
+    private static void visitConditions(Condition rootCondition, 
ConditionVisitor visitor) {
+        visitor.visit(rootCondition);
+        // recursive call for sub-conditions as parameters
+        for (Object parameterValue : 
rootCondition.getParameterValues().values()) {
+            if (parameterValue instanceof Condition) {
+                Condition parameterValueCondition = (Condition) parameterValue;
+                visitConditions(parameterValueCondition, visitor);
+            } else if (parameterValue instanceof Collection) {
+                @SuppressWarnings("unchecked")
+                Collection<Object> valueList = (Collection<Object>) 
parameterValue;
+                for (Object value : valueList) {
+                    if (value instanceof Condition) {
+                        Condition valueCondition = (Condition) value;
+                        visitConditions(valueCondition, visitor);
+                    }
+                }
+            }
+        }
+    }
+
+    public static boolean resolveActionTypes(DefinitionsService 
definitionsService, List<Action> actions) {
+        boolean result = true;
+        for (Action action : actions) {
+            result &= ParserHelper.resolveActionType(definitionsService, 
action);
+        }
+        return result;
+    }
+
+    public static boolean resolveActionType(DefinitionsService 
definitionsService, Action action) {
+        if (action.getActionType() == null) {
+            ActionType actionType = 
definitionsService.getActionType(action.getActionTypeId());
+            if (actionType != null) {
+                action.setActionType(actionType);
+            } else {
+                logger.warn("Couldn't resolve action types : " + 
action.getActionTypeId());
+                return false;
+            }
+        }
+        return true;
+    }
+
+    public static void resolveValueType(DefinitionsService definitionsService, 
PropertyType propertyType) {
+        if (propertyType.getValueType() == null) {
+            ValueType valueType = 
definitionsService.getValueType(propertyType.getValueTypeId());
+            if (valueType != null) {
+                propertyType.setValueType(valueType);
+            }
+        }
+    }
+
+    interface ConditionVisitor {
+        void visit(Condition condition);
+    }
+}

http://git-wip-us.apache.org/repos/asf/incubator-unomi/blob/dc1d1520/services/src/main/java/org/apache/unomi/services/services/ProfileServiceImpl.java
----------------------------------------------------------------------
diff --git 
a/services/src/main/java/org/apache/unomi/services/services/ProfileServiceImpl.java
 
b/services/src/main/java/org/apache/unomi/services/services/ProfileServiceImpl.java
new file mode 100644
index 0000000..e097111
--- /dev/null
+++ 
b/services/src/main/java/org/apache/unomi/services/services/ProfileServiceImpl.java
@@ -0,0 +1,701 @@
+/*
+ * 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.conditions.Condition;
+import org.apache.unomi.api.conditions.ConditionType;
+import org.apache.unomi.api.query.Query;
+import org.apache.unomi.api.services.DefinitionsService;
+import org.apache.unomi.api.services.ProfileService;
+import org.apache.unomi.api.services.QueryService;
+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.actions.ActionExecutorDispatcher;
+import org.osgi.framework.*;
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
+
+import java.io.IOException;
+import java.net.URL;
+import java.util.*;
+
+public class ProfileServiceImpl implements ProfileService, 
SynchronousBundleListener {
+
+    private static final Logger logger = 
LoggerFactory.getLogger(ProfileServiceImpl.class.getName());
+
+    private BundleContext bundleContext;
+
+    private PersistenceService persistenceService;
+
+    private DefinitionsService definitionsService;
+
+    private QueryService queryService;
+
+    private ActionExecutorDispatcher actionExecutorDispatcher;
+
+    private Condition purgeProfileQuery;
+    private Integer purgeProfileExistTime = 0;
+    private Integer purgeProfileInactiveTime = 0;
+    private Integer purgeSessionsAndEventsTime = 0;
+    private Integer purgeProfileInterval = 0;
+
+    private Timer allPropertyTypesTimer;
+
+    private Timer purgeProfileTimer;
+
+    private List<PropertyType> allPropertyTypes;
+
+    public ProfileServiceImpl() {
+        logger.info("Initializing profile service...");
+    }
+
+    public void setBundleContext(BundleContext bundleContext) {
+        this.bundleContext = bundleContext;
+    }
+
+    public void setPersistenceService(PersistenceService persistenceService) {
+        this.persistenceService = persistenceService;
+    }
+
+    public void setDefinitionsService(DefinitionsService definitionsService) {
+        this.definitionsService = definitionsService;
+    }
+
+    public void postConstruct() {
+        logger.debug("postConstruct {" + bundleContext.getBundle() + "}");
+
+        processBundleStartup(bundleContext);
+        for (Bundle bundle : bundleContext.getBundles()) {
+            if (bundle.getBundleContext() != null) {
+                processBundleStartup(bundle.getBundleContext());
+            }
+        }
+        bundleContext.addBundleListener(this);
+        initializePurge();
+        schedulePropertyTypeLoad();
+    }
+
+    public void preDestroy() {
+        bundleContext.removeBundleListener(this);
+        cancelPurge();
+        cancelPropertyTypeLoad();
+    }
+
+    private void processBundleStartup(BundleContext bundleContext) {
+        if (bundleContext == null) {
+            return;
+        }
+        loadPredefinedPersonas(bundleContext);
+        loadPredefinedPropertyTypes(bundleContext);
+    }
+
+    private void processBundleStop(BundleContext bundleContext) {
+    }
+
+    public void setQueryService(QueryService queryService) {
+        this.queryService = queryService;
+    }
+
+    public void setPurgeProfileExistTime(Integer purgeProfileExistTime) {
+        this.purgeProfileExistTime = purgeProfileExistTime;
+    }
+
+    public void setPurgeProfileInactiveTime(Integer purgeProfileInactiveTime) {
+        this.purgeProfileInactiveTime = purgeProfileInactiveTime;
+    }
+
+    public void setPurgeSessionsAndEventsTime(Integer 
purgeSessionsAndEventsTime) {
+        this.purgeSessionsAndEventsTime = purgeSessionsAndEventsTime;
+    }
+
+    public void setPurgeProfileInterval(Integer purgeProfileInterval) {
+        this.purgeProfileInterval = purgeProfileInterval;
+    }
+
+    private void schedulePropertyTypeLoad() {
+        allPropertyTypesTimer = new Timer();
+        TimerTask task = new TimerTask() {
+            @Override
+            public void run() {
+                try {
+                    allPropertyTypes = 
persistenceService.getAllItems(PropertyType.class);
+                } catch (Exception e) {
+                    logger.error(e.getMessage(), e);
+                }
+            }
+        };
+        allPropertyTypesTimer.scheduleAtFixedRate(task, 0, 5000);
+        logger.info("Scheduled task for property type loading each 5s");
+    }
+
+    private void cancelPropertyTypeLoad() {
+        if (allPropertyTypesTimer != null) {
+            allPropertyTypesTimer.cancel();
+            logger.info("Cancelled task for property type loading");
+        }
+    }
+
+    private void initializePurge() {
+        logger.info("Profile purge: Initializing");
+
+        if (purgeProfileInactiveTime > 0 || purgeProfileExistTime > 0 || 
purgeSessionsAndEventsTime > 0) {
+            if (purgeProfileInactiveTime > 0) {
+                logger.info("Profile purge: Profile with no visits since {} 
days, will be purged", purgeProfileInactiveTime);
+            }
+            if (purgeProfileExistTime > 0) {
+                logger.info("Profile purge: Profile created since {} days, 
will be purged", purgeProfileExistTime);
+            }
+
+            purgeProfileTimer = new Timer();
+            TimerTask task = new TimerTask() {
+                @Override
+                public void run() {
+                    long t = System.currentTimeMillis();
+                    logger.debug("Profile purge: Purge triggered");
+
+                    if (purgeProfileQuery == null) {
+                        ConditionType profilePropertyConditionType = 
definitionsService.getConditionType("profilePropertyCondition");
+                        ConditionType booleanCondition = 
definitionsService.getConditionType("booleanCondition");
+                        if (profilePropertyConditionType == null || 
booleanCondition == null) {
+                            // definition service not yet fully instantiate
+                            return;
+                        }
+
+                        purgeProfileQuery = new Condition(booleanCondition);
+                        purgeProfileQuery.setParameter("operator", "or");
+                        List<Condition> subConditions = new ArrayList<>();
+
+                        if (purgeProfileInactiveTime > 0) {
+                            Condition inactiveTimeCondition = new 
Condition(profilePropertyConditionType);
+                            inactiveTimeCondition.setParameter("propertyName", 
"lastVisit");
+                            
inactiveTimeCondition.setParameter("comparisonOperator", "lessThanOrEqualTo");
+                            
inactiveTimeCondition.setParameter("propertyValueDateExpr", "now-" + 
purgeProfileInactiveTime + "d");
+                            subConditions.add(inactiveTimeCondition);
+                        }
+
+                        if (purgeProfileExistTime > 0) {
+                            Condition existTimeCondition = new 
Condition(profilePropertyConditionType);
+                            existTimeCondition.setParameter("propertyName", 
"firstVisit");
+                            
existTimeCondition.setParameter("comparisonOperator", "lessThanOrEqualTo");
+                            
existTimeCondition.setParameter("propertyValueDateExpr", "now-" + 
purgeProfileExistTime + "d");
+                            subConditions.add(existTimeCondition);
+                        }
+
+                        purgeProfileQuery.setParameter("subConditions", 
subConditions);
+                    }
+
+                    persistenceService.removeByQuery(purgeProfileQuery, 
Profile.class);
+
+                    if (purgeSessionsAndEventsTime > 0) {
+                        
persistenceService.purge(getMonth(-purgeSessionsAndEventsTime).getTime());
+                    }
+
+                    logger.info("Profile purge: purge executed in {} ms", 
System.currentTimeMillis() - t);
+                }
+            };
+            purgeProfileTimer.scheduleAtFixedRate(task, getDay(1).getTime(), 
purgeProfileInterval * 24L * 60L * 60L * 1000L);
+
+            logger.info("Profile purge: purge scheduled with an interval of {} 
days", purgeProfileInterval);
+        } else {
+            logger.info("Profile purge: No purge scheduled");
+        }
+    }
+
+    private void cancelPurge() {
+        if (purgeProfileTimer != null) {
+            purgeProfileTimer.cancel();
+        }
+        logger.info("Profile purge: Purge unscheduled");
+    }
+
+    private GregorianCalendar getDay(int offset) {
+        GregorianCalendar gc = new GregorianCalendar();
+        gc = new GregorianCalendar(gc.get(Calendar.YEAR), 
gc.get(Calendar.MONTH), gc.get(Calendar.DAY_OF_MONTH));
+        gc.add(Calendar.DAY_OF_MONTH, offset);
+        return gc;
+    }
+
+    private GregorianCalendar getMonth(int offset) {
+        GregorianCalendar gc = new GregorianCalendar();
+        gc = new GregorianCalendar(gc.get(Calendar.YEAR), 
gc.get(Calendar.MONTH), 1);
+        gc.add(Calendar.MONTH, offset);
+        return gc;
+    }
+
+    public long getAllProfilesCount() {
+        Condition condition = new 
Condition(definitionsService.getConditionType("profilePropertyCondition"));
+        condition.setParameter("propertyName", "mergedWith");
+        condition.setParameter("comparisonOperator", "missing");
+        return persistenceService.queryCount(condition, Profile.ITEM_TYPE);
+    }
+
+    public <T extends Profile> PartialList<T> search(Query query, final 
Class<T> clazz) {
+        if (query.getCondition() != null && 
definitionsService.resolveConditionType(query.getCondition())) {
+            if (StringUtils.isNotBlank(query.getText())) {
+                return persistenceService.queryFullText(query.getText(), 
query.getCondition(), query.getSortby(), clazz, query.getOffset(), 
query.getLimit());
+            } else {
+                return persistenceService.query(query.getCondition(), 
query.getSortby(), clazz, query.getOffset(), query.getLimit());
+            }
+        } else {
+            if (StringUtils.isNotBlank(query.getText())) {
+                return persistenceService.queryFullText(query.getText(), 
query.getSortby(), clazz, query.getOffset(), query.getLimit());
+            } else {
+                return persistenceService.getAllItems(clazz, 
query.getOffset(), query.getLimit(), query.getSortby());
+            }
+        }
+    }
+
+    @Override
+    public boolean createPropertyType(PropertyType property) {
+        if (persistenceService.load(property.getItemId(), PropertyType.class) 
!= null) {
+            return false;
+        }
+        return persistenceService.save(property);
+    }
+
+    @Override
+    public boolean deletePropertyType(String propertyId) {
+        return persistenceService.remove(propertyId, PropertyType.class);
+    }
+
+    @Override
+    public Set<PropertyType> getExistingProperties(String tagId, String 
itemType) {
+        Set<PropertyType> filteredProperties = new 
LinkedHashSet<PropertyType>();
+        // TODO: here we limit the result to the definition we have, but what 
if some properties haven't definition but exist in ES mapping ?
+        Set<PropertyType> profileProperties = getPropertyTypeByTag(tagId, 
true);
+        Map<String, Map<String, Object>> itemMapping = 
persistenceService.getMapping(itemType);
+
+        if (itemMapping == null || itemMapping.isEmpty() || 
itemMapping.get("properties") == null || 
itemMapping.get("properties").get("properties") == null) {
+            return filteredProperties;
+        }
+
+        Map<String, Map<String, String>> propMapping = (Map<String, 
Map<String, String>>) itemMapping.get("properties").get("properties");
+        for (PropertyType propertyType : profileProperties) {
+            if (propMapping.containsKey(propertyType.getMetadata().getId())) {
+                filteredProperties.add(propertyType);
+            }
+        }
+        return filteredProperties;
+    }
+
+
+    // TODO: can be improve to use ES mappings directly to read the existing 
properties
+    @Override
+    public String exportProfilesPropertiesToCsv(Query query) {
+        StringBuilder sb = new StringBuilder();
+        Set<PropertyType> profileProperties = 
getExistingProperties("profileProperties", Profile.ITEM_TYPE);
+        PropertyType[] propertyTypes = profileProperties.toArray(new 
PropertyType[profileProperties.size()]);
+        PartialList<Profile> profiles = search(query, Profile.class);
+
+        sb.append("profileId;");
+        // headers
+        for (int i = 0; i < propertyTypes.length; i++) {
+            PropertyType propertyType = propertyTypes[i];
+            sb.append(propertyType.getMetadata().getId());
+            if (i < propertyTypes.length - 1) {
+                sb.append(";");
+            } else {
+                sb.append("\n");
+            }
+        }
+
+        // rows
+        for (Profile profile : profiles.getList()) {
+            sb.append(profile.getItemId());
+            sb.append(";");
+            for (int i = 0; i < propertyTypes.length; i++) {
+                PropertyType propertyType = propertyTypes[i];
+                if 
(profile.getProperties().get(propertyType.getMetadata().getId()) != null) {
+                    handleExportProperty(sb, 
profile.getProperties().get(propertyType.getMetadata().getId()), propertyType);
+                } else {
+                    sb.append("");
+                }
+                if (i < propertyTypes.length - 1) {
+                    sb.append(";");
+                } else {
+                    sb.append("\n");
+                }
+            }
+        }
+
+        return sb.toString();
+    }
+
+    // TODO may be moved this in a specific Export Utils Class and improve it 
to handle date format, ...
+    private void handleExportProperty(StringBuilder sb, Object propertyValue, 
PropertyType propertyType) {
+        if (propertyValue instanceof Collection && 
propertyType.isMultivalued()) {
+            Collection propertyValues = (Collection) propertyValue;
+            if (propertyValues.size() > 0) {
+                Object[] propertyValuesArray = propertyValues.toArray();
+                for (int i = 0; i < propertyValuesArray.length; i++) {
+                    Object o = propertyValuesArray[i];
+                    if (o instanceof String && i == 0) {
+                        sb.append("\"");
+                    }
+                    sb.append(propertyValue.toString());
+                    if (o instanceof String && i == propertyValuesArray.length 
- 1) {
+                        sb.append("\"");
+                    } else {
+                        sb.append(",");
+                    }
+                }
+            }
+        } else {
+            if (propertyValue instanceof String) {
+                sb.append("\"");
+            }
+            sb.append(propertyValue.toString());
+            if (propertyValue instanceof String) {
+                sb.append("\"");
+            }
+        }
+    }
+
+    public PartialList<Profile> findProfilesByPropertyValue(String 
propertyName, String propertyValue, int offset, int size, String sortBy) {
+        return persistenceService.query(propertyName, propertyValue, sortBy, 
Profile.class, offset, size);
+    }
+
+    public Profile load(String profileId) {
+        return persistenceService.load(profileId, Profile.class);
+    }
+
+    public Profile save(Profile profile) {
+        persistenceService.save(profile);
+        return persistenceService.load(profile.getItemId(), Profile.class);
+    }
+
+    public Persona savePersona(Persona profile) {
+        if (persistenceService.load(profile.getItemId(), Persona.class) == 
null) {
+            Session session = new PersonaSession(UUID.randomUUID().toString(), 
profile, new Date());
+            persistenceService.save(profile);
+            persistenceService.save(session);
+        } else {
+            persistenceService.save(profile);
+        }
+
+        return persistenceService.load(profile.getItemId(), Persona.class);
+    }
+
+    public void delete(String profileId, boolean persona) {
+        if (persona) {
+            persistenceService.remove(profileId, Persona.class);
+        } else {
+            Condition mergeCondition = new 
Condition(definitionsService.getConditionType("profilePropertyCondition"));
+            mergeCondition.setParameter("propertyName", "mergedWith");
+            mergeCondition.setParameter("comparisonOperator", "equals");
+            mergeCondition.setParameter("propertyValue", profileId);
+            persistenceService.removeByQuery(mergeCondition, Profile.class);
+
+            persistenceService.remove(profileId, Profile.class);
+        }
+    }
+
+    public Profile mergeProfiles(Profile masterProfile, List<Profile> 
profilesToMerge) {
+
+        // now let's remove all the already merged profiles from the list.
+        List<Profile> filteredProfilesToMerge = new ArrayList<>();
+
+        for (Profile filteredProfile : profilesToMerge) {
+            if 
(!filteredProfile.getItemId().equals(masterProfile.getItemId())) {
+                filteredProfilesToMerge.add(filteredProfile);
+            }
+        }
+
+        if (filteredProfilesToMerge.isEmpty()) {
+            return masterProfile;
+        }
+
+        profilesToMerge = filteredProfilesToMerge;
+
+        Set<String> allProfileProperties = new LinkedHashSet<>();
+        for (Profile profile : profilesToMerge) {
+            allProfileProperties.addAll(profile.getProperties().keySet());
+        }
+
+        Collection<PropertyType> profilePropertyTypes = 
getAllPropertyTypes("profiles");
+        Map<String, PropertyType> profilePropertyTypeById = new HashMap<>();
+        for (PropertyType propertyType : profilePropertyTypes) {
+            profilePropertyTypeById.put(propertyType.getMetadata().getId(), 
propertyType);
+        }
+        Set<String> profileIdsToMerge = new TreeSet<>();
+        for (Profile profileToMerge : profilesToMerge) {
+            profileIdsToMerge.add(profileToMerge.getItemId());
+        }
+        logger.info("Merging profiles " + profileIdsToMerge + " into profile " 
+ masterProfile.getItemId());
+
+        for (String profileProperty : allProfileProperties) {
+            PropertyType propertyType = 
profilePropertyTypeById.get(profileProperty);
+            String propertyMergeStrategyId = "defaultMergeStrategy";
+            if (propertyType != null) {
+                if (propertyType.getMergeStrategy() != null && 
propertyMergeStrategyId.length() > 0) {
+                    propertyMergeStrategyId = propertyType.getMergeStrategy();
+                }
+            }
+            PropertyMergeStrategyType propertyMergeStrategyType = 
definitionsService.getPropertyMergeStrategyType(propertyMergeStrategyId);
+            if (propertyMergeStrategyType == null) {
+                // we couldn't find the strategy
+                if (propertyMergeStrategyId.equals("defaultMergeStrategy")) {
+                    logger.warn("Couldn't resolve default strategy, ignoring 
property merge for property " + profileProperty);
+                    continue;
+                } else {
+                    // todo: improper algorithm… it is possible that the 
defaultMergeStrategy couldn't be resolved here
+                    logger.warn("Couldn't resolve strategy " + 
propertyMergeStrategyId + " for property " + profileProperty + ", using default 
strategy instead");
+                    propertyMergeStrategyId = "defaultMergeStrategy";
+                    propertyMergeStrategyType = 
definitionsService.getPropertyMergeStrategyType(propertyMergeStrategyId);
+                }
+            }
+
+            // todo: find a way to avoid resolving 
PropertyMergeStrategyExecutor every time?
+            Collection<ServiceReference<PropertyMergeStrategyExecutor>> 
matchingPropertyMergeStrategyExecutors;
+            try {
+                matchingPropertyMergeStrategyExecutors = 
bundleContext.getServiceReferences(PropertyMergeStrategyExecutor.class, 
propertyMergeStrategyType.getFilter());
+                for (ServiceReference<PropertyMergeStrategyExecutor> 
propertyMergeStrategyExecutorReference : 
matchingPropertyMergeStrategyExecutors) {
+                    PropertyMergeStrategyExecutor 
propertyMergeStrategyExecutor = 
bundleContext.getService(propertyMergeStrategyExecutorReference);
+                    
propertyMergeStrategyExecutor.mergeProperty(profileProperty, propertyType, 
profilesToMerge, masterProfile);
+                }
+            } catch (InvalidSyntaxException e) {
+                logger.error("Error retrieving strategy implementation", e);
+            }
+
+        }
+
+        // we now have to merge the profile's segments
+        for (Profile profile : profilesToMerge) {
+            masterProfile.getSegments().addAll(profile.getSegments());
+        }
+
+        return masterProfile;
+    }
+
+    public PartialList<Session> getProfileSessions(String profileId, String 
query, int offset, int size, String sortBy) {
+        if (StringUtils.isNotBlank(query)) {
+            return persistenceService.queryFullText("profileId", profileId, 
query, sortBy, Session.class, offset, size);
+        } else {
+            return persistenceService.query("profileId", profileId, sortBy, 
Session.class, offset, size);
+        }
+    }
+
+    public String getPropertyTypeMapping(String fromPropertyTypeId) {
+        Collection<PropertyType> types = 
getPropertyTypeByMapping(fromPropertyTypeId);
+        if (types.size() > 0) {
+            return types.iterator().next().getMetadata().getId();
+        }
+        return null;
+    }
+
+    public Session loadSession(String sessionId, Date dateHint) {
+        Session s = persistenceService.load(sessionId, dateHint, 
Session.class);
+        if (s == null && dateHint != null) {
+            GregorianCalendar gc = new GregorianCalendar();
+            gc.setTime(dateHint);
+            if (gc.get(Calendar.DAY_OF_MONTH) == 1) {
+                gc.add(Calendar.DAY_OF_MONTH, -1);
+                s = persistenceService.load(sessionId, gc.getTime(), 
Session.class);
+            }
+        }
+        return s;
+    }
+
+    public Session saveSession(Session session) {
+        return persistenceService.save(session) ? session : null;
+    }
+
+    public PartialList<Session> findProfileSessions(String profileId) {
+        return persistenceService.query("profileId", profileId, 
"timeStamp:desc", Session.class, 0, 50);
+    }
+
+    @Override
+    public boolean matchCondition(Condition condition, Profile profile, 
Session session) {
+        ParserHelper.resolveConditionType(definitionsService, condition);
+        Condition profileCondition = 
definitionsService.extractConditionByTag(condition, "profileCondition");
+        Condition sessionCondition = 
definitionsService.extractConditionByTag(condition, "sessionCondition");
+        if (profileCondition != null && 
!persistenceService.testMatch(profileCondition, profile)) {
+            return false;
+        }
+        return !(sessionCondition != null && 
!persistenceService.testMatch(sessionCondition, session));
+    }
+
+    public void batchProfilesUpdate(BatchUpdate update) {
+        ParserHelper.resolveConditionType(definitionsService, 
update.getCondition());
+        List<Profile> profiles = 
persistenceService.query(update.getCondition(), null, Profile.class);
+
+        for (Profile profile : profiles) {
+            if (PropertyHelper.setProperty(profile, update.getPropertyName(), 
update.getPropertyValue(), update.getStrategy())) {
+//                Event profileUpdated = new Event("profileUpdated", null, 
profile, null, null, profile, new Date());
+//                profileUpdated.setPersistent(false);
+//                eventService.send(profileUpdated);
+                save(profile);
+            }
+        }
+    }
+
+    public Persona loadPersona(String personaId) {
+        return persistenceService.load(personaId, Persona.class);
+    }
+
+    public PersonaWithSessions loadPersonaWithSessions(String personaId) {
+        Persona persona = persistenceService.load(personaId, Persona.class);
+        if (persona == null) {
+            return null;
+        }
+        List<PersonaSession> sessions = persistenceService.query("profileId", 
persona.getItemId(), "timeStamp:desc", PersonaSession.class);
+        return new PersonaWithSessions(persona, sessions);
+    }
+
+    public Persona createPersona(String personaId) {
+        Persona newPersona = new Persona(personaId);
+
+        Session session = new PersonaSession(UUID.randomUUID().toString(), 
newPersona, new Date());
+
+        persistenceService.save(newPersona);
+        persistenceService.save(session);
+        return newPersona;
+    }
+
+
+    public Collection<PropertyType> getAllPropertyTypes(String target) {
+        return persistenceService.query("target", target, null, 
PropertyType.class);
+    }
+
+    public HashMap<String, Collection<PropertyType>> getAllPropertyTypes() {
+        Collection<PropertyType> props = 
persistenceService.getAllItems(PropertyType.class, 0, -1, "rank").getList();
+
+        HashMap<String, Collection<PropertyType>> propertyTypes = new 
HashMap<>();
+        for (PropertyType prop : props) {
+            if (!propertyTypes.containsKey(prop.getTarget())) {
+                propertyTypes.put(prop.getTarget(), new 
LinkedHashSet<PropertyType>());
+            }
+            propertyTypes.get(prop.getTarget()).add(prop);
+        }
+        return propertyTypes;
+    }
+
+    public Set<PropertyType> getPropertyTypeByTag(String tag, boolean 
includeFromSubtags) {
+        Set<PropertyType> propertyTypes = new LinkedHashSet<PropertyType>();
+        Collection<PropertyType> directPropertyTypes = 
persistenceService.query("tags", tag, "rank", PropertyType.class);
+
+        if (directPropertyTypes != null) {
+            propertyTypes.addAll(directPropertyTypes);
+        }
+        if (includeFromSubtags) {
+            for (Tag subTag : definitionsService.getTag(tag).getSubTags()) {
+                Set<PropertyType> childPropertyTypes = 
getPropertyTypeByTag(subTag.getId(), true);
+                propertyTypes.addAll(childPropertyTypes);
+            }
+        }
+        return propertyTypes;
+    }
+
+    public Collection<PropertyType> getPropertyTypeByMapping(String 
propertyName) {
+        Collection<PropertyType> l = new TreeSet<PropertyType>(new 
Comparator<PropertyType>() {
+            @Override
+            public int compare(PropertyType o1, PropertyType o2) {
+                if (o1.getRank() == o2.getRank()) {
+                    return 
o1.getMetadata().getName().compareTo(o1.getMetadata().getName());
+                } else if (o1.getRank() < o2.getRank()) {
+                    return -1;
+                } else {
+                    return 1;
+                }
+            }
+        });
+
+        for (PropertyType propertyType : allPropertyTypes) {
+            if (propertyType.getAutomaticMappingsFrom() != null && 
propertyType.getAutomaticMappingsFrom().contains(propertyName)) {
+                l.add(propertyType);
+            }
+        }
+        return l;
+    }
+
+    public PropertyType getPropertyType(String id) {
+        return persistenceService.load(id, PropertyType.class);
+    }
+
+    public PartialList<Session> getPersonaSessions(String personaId, int 
offset, int size, String sortBy) {
+        return persistenceService.query("profileId", personaId, sortBy, 
Session.class, offset, size);
+    }
+
+    private void loadPredefinedPersonas(BundleContext bundleContext) {
+        if (bundleContext == null) {
+            return;
+        }
+        Enumeration<URL> predefinedPersonaEntries = 
bundleContext.getBundle().findEntries("META-INF/cxs/personas", "*.json", true);
+        if (predefinedPersonaEntries == null) {
+            return;
+        }
+
+        while (predefinedPersonaEntries.hasMoreElements()) {
+            URL predefinedPersonaURL = predefinedPersonaEntries.nextElement();
+            logger.debug("Found predefined persona at " + predefinedPersonaURL 
+ ", loading... ");
+
+            try {
+                PersonaWithSessions persona = 
CustomObjectMapper.getObjectMapper().readValue(predefinedPersonaURL, 
PersonaWithSessions.class);
+                persistenceService.save(persona.getPersona());
+
+                List<PersonaSession> sessions = persona.getSessions();
+                for (PersonaSession session : sessions) {
+                    session.setProfile(persona.getPersona());
+                    persistenceService.save(session);
+                }
+            } catch (IOException e) {
+                logger.error("Error while loading persona " + 
predefinedPersonaURL, e);
+            }
+
+        }
+    }
+
+    private void loadPredefinedPropertyTypes(BundleContext bundleContext) {
+        Enumeration<URL> predefinedPropertyTypeEntries = 
bundleContext.getBundle().findEntries("META-INF/cxs/properties", "*.json", 
true);
+        if (predefinedPropertyTypeEntries == null) {
+            return;
+        }
+
+        while (predefinedPropertyTypeEntries.hasMoreElements()) {
+            URL predefinedPropertyTypeURL = 
predefinedPropertyTypeEntries.nextElement();
+            logger.debug("Found predefined property type at " + 
predefinedPropertyTypeURL + ", loading... ");
+
+            try {
+                PropertyType propertyType = 
CustomObjectMapper.getObjectMapper().readValue(predefinedPropertyTypeURL, 
PropertyType.class);
+                String[] splitPath = 
predefinedPropertyTypeURL.getPath().split("/");
+                String target = splitPath[4];
+                propertyType.setTarget(target);
+
+                persistenceService.save(propertyType);
+            } catch (IOException e) {
+                logger.error("Error while loading properties " + 
predefinedPropertyTypeURL, e);
+            }
+        }
+    }
+
+
+    public void bundleChanged(BundleEvent event) {
+        switch (event.getType()) {
+            case BundleEvent.STARTED:
+                processBundleStartup(event.getBundle().getBundleContext());
+                break;
+            case BundleEvent.STOPPING:
+                processBundleStop(event.getBundle().getBundleContext());
+                break;
+        }
+    }
+
+}

http://git-wip-us.apache.org/repos/asf/incubator-unomi/blob/dc1d1520/services/src/main/java/org/apache/unomi/services/services/QueryServiceImpl.java
----------------------------------------------------------------------
diff --git 
a/services/src/main/java/org/apache/unomi/services/services/QueryServiceImpl.java
 
b/services/src/main/java/org/apache/unomi/services/services/QueryServiceImpl.java
new file mode 100644
index 0000000..33e4f47
--- /dev/null
+++ 
b/services/src/main/java/org/apache/unomi/services/services/QueryServiceImpl.java
@@ -0,0 +1,109 @@
+/*
+ * 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.conditions.Condition;
+import org.apache.unomi.api.query.AggregateQuery;
+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.slf4j.Logger;
+import org.slf4j.LoggerFactory;
+
+import java.util.Map;
+
+public class QueryServiceImpl implements QueryService {
+    private static final Logger logger = 
LoggerFactory.getLogger(QueryServiceImpl.class.getName());
+
+    private PersistenceService persistenceService;
+
+    private DefinitionsService definitionsService;
+
+    public void setPersistenceService(PersistenceService persistenceService) {
+        this.persistenceService = persistenceService;
+    }
+
+    public void setDefinitionsService(DefinitionsService definitionsService) {
+        this.definitionsService = definitionsService;
+    }
+
+    public void postConstruct() {
+    }
+
+    public void preDestroy() {
+    }
+
+    @Override
+    public Map<String, Long> getAggregate(String itemType, String property) {
+        return persistenceService.aggregateQuery(null, new 
TermsAggregate(property), itemType);
+    }
+
+    @Override
+    public Map<String, Long> getAggregate(String itemType, String property, 
AggregateQuery query) {
+        if (query != null) {
+            // resolve condition
+            if (query.getCondition() != null) {
+                ParserHelper.resolveConditionType(definitionsService, 
query.getCondition());
+            }
+
+            // resolve aggregate
+            if (query.getAggregate() != null) {
+                String aggregateType = query.getAggregate().getType();
+                if (aggregateType != null) {
+                    // try to guess the aggregate type
+                    if (aggregateType.equals("date")) {
+                        String interval = (String) 
query.getAggregate().getParameters().get("interval");
+                        String format = (String) 
query.getAggregate().getParameters().get("format");
+                        return 
persistenceService.aggregateQuery(query.getCondition(), new 
DateAggregate(property, interval, format), itemType);
+                    } else if (aggregateType.equals("dateRange") && 
query.getAggregate().getDateRanges() != null && 
query.getAggregate().getDateRanges().size() > 0) {
+                        String format = (String) 
query.getAggregate().getParameters().get("format");
+                        return 
persistenceService.aggregateQuery(query.getCondition(), new 
DateRangeAggregate(query.getAggregate().getProperty(), format, 
query.getAggregate().getDateRanges()), itemType);
+                    } else if (aggregateType.equals("numericRange") && 
query.getAggregate().getNumericRanges() != null && 
query.getAggregate().getNumericRanges().size() > 0) {
+                        return 
persistenceService.aggregateQuery(query.getCondition(), new 
NumericRangeAggregate(query.getAggregate().getProperty(), 
query.getAggregate().getNumericRanges()), itemType);
+                    } else if (aggregateType.equals("ipRange") && 
query.getAggregate().ipRanges() != null && 
query.getAggregate().ipRanges().size() > 0) {
+                        return 
persistenceService.aggregateQuery(query.getCondition(), new 
IpRangeAggregate(query.getAggregate().getProperty(), 
query.getAggregate().ipRanges()), itemType);
+                    }
+                }
+            }
+
+            // fall back on terms aggregate
+            return persistenceService.aggregateQuery(query.getCondition(), new 
TermsAggregate(property), itemType);
+        }
+
+        return getAggregate(itemType, property);
+    }
+
+    @Override
+    public Map<String, Double> getMetric(String type, String property, String 
slashConcatenatedMetrics, Condition condition) {
+        if (condition.getConditionType() == null) {
+            ParserHelper.resolveConditionType(definitionsService, condition);
+        }
+        return persistenceService.getSingleValuesMetrics(condition, 
slashConcatenatedMetrics.split("/"), property, type);
+    }
+
+    @Override
+    public long getQueryCount(String itemType, Condition condition) {
+        if (condition.getConditionType() == null) {
+            ParserHelper.resolveConditionType(definitionsService, condition);
+        }
+        return persistenceService.queryCount(condition, itemType);
+    }
+
+
+}
\ No newline at end of file

Reply via email to