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
