http://git-wip-us.apache.org/repos/asf/incubator-unomi/blob/dc1d1520/services/src/main/java/org/oasis_open/contextserver/impl/services/SegmentServiceImpl.java ---------------------------------------------------------------------- diff --git a/services/src/main/java/org/oasis_open/contextserver/impl/services/SegmentServiceImpl.java b/services/src/main/java/org/oasis_open/contextserver/impl/services/SegmentServiceImpl.java deleted file mode 100644 index e853959..0000000 --- a/services/src/main/java/org/oasis_open/contextserver/impl/services/SegmentServiceImpl.java +++ /dev/null @@ -1,818 +0,0 @@ -/* - * 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.oasis_open.contextserver.impl.services; - -import com.fasterxml.jackson.core.JsonProcessingException; -import org.oasis_open.contextserver.api.*; -import org.oasis_open.contextserver.api.actions.Action; -import org.oasis_open.contextserver.api.conditions.Condition; -import org.oasis_open.contextserver.api.conditions.ConditionType; -import org.oasis_open.contextserver.api.query.Query; -import org.oasis_open.contextserver.api.rules.Rule; -import org.oasis_open.contextserver.api.segments.Scoring; -import org.oasis_open.contextserver.api.segments.ScoringElement; -import org.oasis_open.contextserver.api.segments.Segment; -import org.oasis_open.contextserver.api.segments.SegmentsAndScores; -import org.oasis_open.contextserver.api.services.DefinitionsService; -import org.oasis_open.contextserver.api.services.RulesService; -import org.oasis_open.contextserver.api.services.SegmentService; -import org.oasis_open.contextserver.persistence.spi.CustomObjectMapper; -import org.oasis_open.contextserver.persistence.spi.PersistenceService; -import org.oasis_open.contextserver.persistence.spi.aggregate.TermsAggregate; -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 javax.json.*; -import java.io.IOException; -import java.net.URL; -import java.security.MessageDigest; -import java.util.*; - -public class SegmentServiceImpl implements SegmentService, SynchronousBundleListener { - - private static final Logger logger = LoggerFactory.getLogger(SegmentServiceImpl.class.getName()); - - private BundleContext bundleContext; - - private PersistenceService persistenceService; - - private DefinitionsService definitionsService; - - private RulesService rulesService; - - private long taskExecutionPeriod = 24L * 60L * 60L * 1000L; - - public SegmentServiceImpl() { - logger.info("Initializing segment service..."); - } - - private List<Segment> allSegments; - private List<Scoring> allScoring; - - private Timer segmentTimer; - - public static void dumpJSON(JsonValue tree, String key, String depthPrefix) { - if (key != null) - logger.info(depthPrefix + "Key " + key + ": "); - switch (tree.getValueType()) { - case OBJECT: - logger.info(depthPrefix + "OBJECT"); - JsonObject object = (JsonObject) tree; - for (String name : object.keySet()) - dumpJSON(object.get(name), name, depthPrefix + " "); - break; - case ARRAY: - logger.info(depthPrefix + "ARRAY"); - JsonArray array = (JsonArray) tree; - for (JsonValue val : array) - dumpJSON(val, null, depthPrefix + " "); - break; - case STRING: - JsonString st = (JsonString) tree; - logger.info(depthPrefix + "STRING " + st.getString()); - break; - case NUMBER: - JsonNumber num = (JsonNumber) tree; - logger.info(depthPrefix + "NUMBER " + num.toString()); - break; - case TRUE: - case FALSE: - case NULL: - logger.info(depthPrefix + tree.getValueType().toString()); - break; - } - } - - 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() + "}"); - loadPredefinedSegments(bundleContext); - loadPredefinedScorings(bundleContext); - for (Bundle bundle : bundleContext.getBundles()) { - if (bundle.getBundleContext() != null) { - loadPredefinedSegments(bundle.getBundleContext()); - loadPredefinedScorings(bundle.getBundleContext()); - } - } - bundleContext.addBundleListener(this); - initializeTimer(); - } - - public void preDestroy() { - bundleContext.removeBundleListener(this); - cancelTimers(); - } - - private void cancelTimers() { - if(segmentTimer != null) { - segmentTimer.cancel(); - } - logger.info("Segment purge: Purge unscheduled"); - } - - private void processBundleStartup(BundleContext bundleContext) { - if (bundleContext == null) { - return; - } - loadPredefinedSegments(bundleContext); - loadPredefinedScorings(bundleContext); - - List<PluginType> types = definitionsService.getTypesByPlugin().get(bundleContext.getBundle().getBundleId()); - List<String> addedConditions = new ArrayList<String>(); - if (types != null) { - for (PluginType type : types) { - if (type instanceof ConditionType) { - addedConditions.add(((ConditionType) type).getId()); - } - } - } - if (!addedConditions.isEmpty()) { - for (Segment segment : persistenceService.query("missingPlugins", "true", null, Segment.class)) { - boolean succeed = ParserHelper.resolveConditionType(definitionsService, segment.getCondition()); - if (succeed) { - logger.info("Enable segment " + segment.getItemId()); - segment.getMetadata().setMissingPlugins(false); - setSegmentDefinition(segment); - } - } - } - } - - 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 (Segment segment : persistenceService.getAllItems(Segment.class)) { - List<String> conditions = ParserHelper.getConditionTypeIds(segment.getCondition()); - if (!Collections.disjoint(conditions, removedConditions)) { - logger.info("Disable segment " + segment.getItemId()); - segment.getMetadata().setMissingPlugins(true); - setSegmentDefinition(segment); - } - } - } - } - - private void loadPredefinedSegments(BundleContext bundleContext) { - Enumeration<URL> predefinedSegmentEntries = bundleContext.getBundle().findEntries("META-INF/cxs/segments", "*.json", true); - if (predefinedSegmentEntries == null) { - return; - } - while (predefinedSegmentEntries.hasMoreElements()) { - URL predefinedSegmentURL = predefinedSegmentEntries.nextElement(); - logger.debug("Found predefined segment at " + predefinedSegmentURL + ", loading... "); - - try { - Segment segment = CustomObjectMapper.getObjectMapper().readValue(predefinedSegmentURL, Segment.class); - if (segment.getMetadata().getScope() == null) { - segment.getMetadata().setScope("systemscope"); - } - if (getSegmentDefinition(segment.getMetadata().getId()) == null) { - setSegmentDefinition(segment); - } - } catch (IOException e) { - logger.error("Error while loading segment definition " + predefinedSegmentURL, e); - } - } - } - - private void loadPredefinedScorings(BundleContext bundleContext) { - Enumeration<URL> predefinedScoringEntries = bundleContext.getBundle().findEntries("META-INF/cxs/scoring", "*.json", true); - if (predefinedScoringEntries == null) { - return; - } - while (predefinedScoringEntries.hasMoreElements()) { - URL predefinedScoringURL = predefinedScoringEntries.nextElement(); - logger.debug("Found predefined scoring at " + predefinedScoringURL + ", loading... "); - - try { - Scoring scoring = CustomObjectMapper.getObjectMapper().readValue(predefinedScoringURL, Scoring.class); - if (scoring.getMetadata().getScope() == null) { - scoring.getMetadata().setScope("systemscope"); - } - if (getScoringDefinition(scoring.getMetadata().getId()) == null) { - setScoringDefinition(scoring); - } - } catch (IOException e) { - logger.error("Error while loading segment definition " + predefinedScoringURL, e); - } - } - } - - public PartialList<Metadata> getSegmentMetadatas(int offset, int size, String sortBy) { - return getMetadatas(offset, size, sortBy, Segment.class); - } - - public PartialList<Metadata> getSegmentMetadatas(String scope, int offset, int size, String sortBy) { - PartialList<Segment> segments = persistenceService.query("metadata.scope", scope, sortBy, Segment.class, offset, size); - List<Metadata> details = new LinkedList<>(); - for (Segment definition : segments.getList()) { - details.add(definition.getMetadata()); - } - return new PartialList<>(details, segments.getOffset(), segments.getPageSize(), segments.getTotalSize()); - } - - public PartialList<Metadata> getSegmentMetadatas(Query query) { - return getMetadatas(query, Segment.class); - } - - private List<Segment> getAllSegmentDefinitions() { - List<Segment> allItems = persistenceService.getAllItems(Segment.class); - for (Segment segment : allItems) { - ParserHelper.resolveConditionType(definitionsService, segment.getCondition()); - } - return allItems; - } - - public Segment getSegmentDefinition(String segmentId) { - Segment definition = persistenceService.load(segmentId, Segment.class); - if (definition != null) { - ParserHelper.resolveConditionType(definitionsService, definition.getCondition()); - } - return definition; - } - - public void setSegmentDefinition(Segment segment) { - ParserHelper.resolveConditionType(definitionsService, segment.getCondition()); - if (segment.getMetadata().isEnabled() && !segment.getMetadata().isMissingPlugins()) { - updateAutoGeneratedRules(segment.getMetadata(), segment.getCondition()); - } - // make sure we update the name and description metadata that might not match, so first we remove the entry from the map - persistenceService.save(segment); - - updateExistingProfilesForSegment(segment); - } - - private void checkIfSegmentIsImpacted(Segment segment, Condition condition, String segmentToDeleteId, Set<Segment> impactedSegments) { - if(condition != null) { - @SuppressWarnings("unchecked") - final List<Condition> subConditions = (List<Condition>) condition.getParameter("subConditions"); - if (subConditions != null) { - for (Condition subCondition : subConditions) { - checkIfSegmentIsImpacted(segment, subCondition, segmentToDeleteId, impactedSegments); - } - } else if ("profileSegmentCondition".equals(condition.getConditionTypeId())) { - @SuppressWarnings("unchecked") - final List<String> referencedSegmentIds = (List<String>) condition.getParameter("segments"); - - if (referencedSegmentIds.indexOf(segmentToDeleteId) >= 0) { - impactedSegments.add(segment); - } - } - } - } - - /** - * Return an updated condition that do not contain a condition on the segmentId anymore - * it's remove the unnecessary boolean condition (if a condition is the only one of a boolean the boolean will be remove and the subcondition returned) - * it's return null when there is no more condition after (if the condition passed was only a segment condition on the segmentId) - * @param condition the condition to update - * @param segmentId the segment id to remove in the condition - * @return updated condition - */ - private Condition updateImpactedCondition(Condition condition, String segmentId) { - if ("booleanCondition".equals(condition.getConditionTypeId())) { - @SuppressWarnings("unchecked") - final List<Condition> subConditions = (List<Condition>) condition.getParameter("subConditions"); - List<Condition> updatedSubConditions = new LinkedList<>(); - for (Condition subCondition : subConditions) { - Condition updatedCondition = updateImpactedCondition(subCondition, segmentId); - if(updatedCondition != null) { - updatedSubConditions.add(updatedCondition); - } - } - if(!updatedSubConditions.isEmpty()){ - if(updatedSubConditions.size() == 1) { - return updatedSubConditions.get(0); - } else { - condition.setParameter("subConditions", updatedSubConditions); - return condition; - } - } else { - return null; - } - } else if("profileSegmentCondition".equals(condition.getConditionTypeId())) { - @SuppressWarnings("unchecked") - final List<String> referencedSegmentIds = (List<String>) condition.getParameter("segments"); - if (referencedSegmentIds.indexOf(segmentId) >= 0) { - referencedSegmentIds.remove(segmentId); - if(referencedSegmentIds.isEmpty()) { - return null; - } else { - condition.setParameter("segments", referencedSegmentIds); - } - } - } - return condition; - } - - private Set<Segment> getImpactedSegments(String segmentId) { - Set<Segment> impactedSegments = new HashSet<>(this.allSegments.size()); - for (Segment segment : this.allSegments) { - checkIfSegmentIsImpacted(segment, segment.getCondition(), segmentId, impactedSegments); - } - return impactedSegments; - } - - public List<Metadata> getImpactedSegmentMetadata(String segmentId) { - List<Metadata> details = new LinkedList<>(); - for (Segment definition : getImpactedSegments(segmentId)) { - details.add(definition.getMetadata()); - } - - return details; - } - - public List<Metadata> removeSegmentDefinition(String segmentId, boolean validate) { - Set<Segment> impactedSegments = getImpactedSegments(segmentId); - if (!validate || impactedSegments.isEmpty()) { - // update profiles - Condition segmentCondition = new Condition(); - segmentCondition.setConditionType(definitionsService.getConditionType("profilePropertyCondition")); - segmentCondition.setParameter("propertyName", "segments"); - segmentCondition.setParameter("comparisonOperator", "equals"); - segmentCondition.setParameter("propertyValue", segmentId); - - List<Profile> previousProfiles = persistenceService.query(segmentCondition, null, Profile.class); - for (Profile profileToRemove : previousProfiles) { - profileToRemove.getSegments().remove(segmentId); - persistenceService.update(profileToRemove.getItemId(), null, Profile.class, "segments", profileToRemove.getSegments()); - } - - // update impacted segments - for (Segment segment : impactedSegments) { - Condition updatedCondition = updateImpactedCondition(segment.getCondition(), segmentId); - segment.setCondition(updatedCondition); - if(updatedCondition == null) { - clearAutoGeneratedRules(persistenceService.query("linkedItems", segment.getMetadata().getId(), null, Rule.class), segment.getMetadata().getId()); - segment.getMetadata().setEnabled(false); - } - setSegmentDefinition(segment); - } - - persistenceService.remove(segmentId, Segment.class); - List<Rule> previousRules = persistenceService.query("linkedItems", segmentId, null, Rule.class); - clearAutoGeneratedRules(previousRules, segmentId); - } - - List<Metadata> metadata = new LinkedList<>(); - for (Segment definition : impactedSegments) { - metadata.add(definition.getMetadata()); - } - return metadata; - } - - - public PartialList<Profile> getMatchingIndividuals(String segmentID, int offset, int size, String sortBy) { - Segment segment = getSegmentDefinition(segmentID); - if (segment == null) { - return new PartialList<Profile>(); - } - return persistenceService.query(segment.getCondition(), sortBy, Profile.class, offset, size); - } - - public long getMatchingIndividualsCount(String segmentID) { - if (getSegmentDefinition(segmentID) == null) { - return 0; - } - - Condition excludeMergedProfilesCondition = new Condition(definitionsService.getConditionType("profilePropertyCondition")); - excludeMergedProfilesCondition.setParameter("propertyName", "mergedWith"); - excludeMergedProfilesCondition.setParameter("comparisonOperator", "missing"); - Condition condition = new Condition(definitionsService.getConditionType("booleanCondition")); - condition.setParameter("operator", "and"); - condition.setParameter("subConditions", Arrays.asList(getSegmentDefinition(segmentID).getCondition(), excludeMergedProfilesCondition)); - - return persistenceService.queryCount(condition, Profile.ITEM_TYPE); - } - - public Boolean isProfileInSegment(Profile profile, String segmentId) { - Set<String> matchingSegments = getSegmentsAndScoresForProfile(profile).getSegments(); - - return matchingSegments.contains(segmentId); - } - - public SegmentsAndScores getSegmentsAndScoresForProfile(Profile profile) { - Set<String> segments = new HashSet<String>(); - Map<String,Integer> scores = new HashMap<String, Integer>(); - - List<Segment> allSegments = this.allSegments; - for (Segment segment : allSegments) { - if (persistenceService.testMatch(segment.getCondition(), profile)) { - segments.add(segment.getMetadata().getId()); - } - } - - List<Scoring> allScoring = this.allScoring; - for (Scoring scoring : allScoring) { - if (scoring.getMetadata().isEnabled()) { - int score = 0; - for (ScoringElement scoringElement : scoring.getElements()) { - if (persistenceService.testMatch(scoringElement.getCondition(), profile)) { - score += scoringElement.getValue(); - } - } - if (score > 0) { - scores.put(scoring.getMetadata().getId(), score); - } - } - } - - return new SegmentsAndScores(segments, scores); - } - - public List<Metadata> getSegmentMetadatasForProfile(Profile profile) { - List<Metadata> metadatas = new ArrayList<>(); - - List<Segment> allSegments = this.allSegments; - for (Segment segment : allSegments) { - if (persistenceService.testMatch(segment.getCondition(), profile)) { - metadatas.add(segment.getMetadata()); - } - } - - return metadatas; - } - - public PartialList<Metadata> getScoringMetadatas(int offset, int size, String sortBy) { - return getMetadatas(offset, size, sortBy, Scoring.class); - } - - public PartialList<Metadata> getScoringMetadatas(Query query) { - return getMetadatas(query, Scoring.class); - } - - private List<Scoring> getAllScoringDefinitions() { - List<Scoring> allItems = persistenceService.getAllItems(Scoring.class); - for (Scoring scoring : allItems) { - for (ScoringElement element : scoring.getElements()) { - ParserHelper.resolveConditionType(definitionsService, element.getCondition()); - } - } - return allItems; - } - - public Scoring getScoringDefinition(String scoringId) { - Scoring definition = persistenceService.load(scoringId, Scoring.class); - if (definition != null) { - for (ScoringElement element : definition.getElements()) { - ParserHelper.resolveConditionType(definitionsService, element.getCondition()); - } - } - return definition; - } - - public void setScoringDefinition(Scoring scoring) { - for (ScoringElement element : scoring.getElements()) { - ParserHelper.resolveConditionType(definitionsService, element.getCondition()); - } - for (ScoringElement element : scoring.getElements()) { - if (scoring.getMetadata().isEnabled() && !scoring.getMetadata().isMissingPlugins()) { - updateAutoGeneratedRules(scoring.getMetadata(), element.getCondition()); - } - } - // make sure we update the name and description metadata that might not match, so first we remove the entry from the map - persistenceService.save(scoring); - - updateExistingProfilesForScoring(scoring); - } - - public void createScoringDefinition(String scope, String scoringId, String name, String description) { - Metadata metadata = new Metadata(scope, scoringId, name, description); - Scoring scoring = new Scoring(metadata); - Condition rootCondition = new Condition(); - rootCondition.setConditionType(definitionsService.getConditionType("booleanCondition")); - rootCondition.setParameter("operator", "and"); - rootCondition.setParameter("subConditions", new ArrayList<Condition>()); - scoring.setElements(new ArrayList<ScoringElement>()); - - setScoringDefinition(scoring); - } - - public void removeScoringDefinition(String scoringId) { - persistenceService.remove(scoringId, Scoring.class); - - updateExistingProfilesForRemovedScoring(scoringId); - } - - public void updateAutoGeneratedRules(Metadata metadata, Condition condition) { - List<Rule> previousRules = persistenceService.query("linkedItems", metadata.getId(), null, Rule.class); - List<Rule> rules = new ArrayList<Rule>(); - if (condition != null) { - getAutoGeneratedRules(metadata, condition, null, rules); - } - for (Rule rule : rules) { - rulesService.setRule(rule); - } - previousRules.removeAll(rules); - clearAutoGeneratedRules(previousRules, metadata.getId()); - } - - private void clearAutoGeneratedRules(List<Rule> rules, String idWithScope) { - for (Rule previousRule : rules) { - previousRule.getLinkedItems().remove(idWithScope); - if (previousRule.getLinkedItems().isEmpty()) { - // todo remove profile properties ? - persistenceService.remove(previousRule.getItemId(), Rule.class); - } else { - persistenceService.update(previousRule.getItemId(), null, Rule.class, "linkedItems", previousRule.getLinkedItems()); - } - } - } - - private void getAutoGeneratedRules(Metadata metadata, Condition condition, Condition parentCondition, List<Rule> rules) { - if (condition.getConditionType().getTagIDs().contains("eventCondition") && !condition.getConditionType().getTagIDs().contains("profileCondition")) { - try { - Map<String,Object> m = new HashMap<>(3); - m.put("scope",metadata.getScope()); - m.put("condition", condition); - m.put("numberOfDays", parentCondition.getParameter("numberOfDays")); - String key = CustomObjectMapper.getObjectMapper().writeValueAsString(m); - key = "eventTriggered" + getMD5(key); - parentCondition.setParameter("generatedPropertyKey", key); - Rule rule = rulesService.getRule(key); - if (rule == null) { - rule = new Rule(new Metadata(metadata.getScope(), key, "Auto generated rule for "+metadata.getName(), "")); - rule.setCondition(condition); - rule.getMetadata().setHidden(true); - final Action action = new Action(); - action.setActionType(definitionsService.getActionType("setEventOccurenceCountAction")); - action.setParameter("pastEventCondition", parentCondition); - - rule.setActions(Arrays.asList(action)); - rule.setLinkedItems(Arrays.asList(metadata.getId())); - rules.add(rule); - - updateExistingProfilesForPastEventCondition(condition, parentCondition); - } else { - rule.getLinkedItems().add(metadata.getId()); - rules.add(rule); - } - } catch (JsonProcessingException e) { - logger.error(e.getMessage(), e); - } - } else { - Collection<Object> values = new ArrayList<>(condition.getParameterValues().values()); - for (Object parameterValue : values) { - if (parameterValue instanceof Condition) { - getAutoGeneratedRules(metadata, (Condition) parameterValue, condition, rules); - } else if (parameterValue instanceof Collection) { - for (Object subCondition : (Collection<?>) parameterValue) { - if (subCondition instanceof Condition) { - getAutoGeneratedRules(metadata, (Condition) subCondition, condition, rules); - } - } - } - } - } - } - - private void updateExistingProfilesForPastEventCondition(Condition eventCondition, Condition parentCondition) { - long t = System.currentTimeMillis(); - List<Condition> l = new ArrayList<Condition>(); - Condition andCondition = new Condition(); - andCondition.setConditionType(definitionsService.getConditionType("booleanCondition")); - andCondition.setParameter("operator", "and"); - andCondition.setParameter("subConditions", l); - - l.add(eventCondition); - - Integer numberOfDays = (Integer) parentCondition.getParameter("numberOfDays"); - if (numberOfDays != null) { - Condition numberOfDaysCondition = new Condition(); - numberOfDaysCondition.setConditionType(definitionsService.getConditionType("sessionPropertyCondition")); - numberOfDaysCondition.setParameter("propertyName", "timeStamp"); - numberOfDaysCondition.setParameter("comparisonOperator", "greaterThan"); - numberOfDaysCondition.setParameter("propertyValue", "now-" + numberOfDays + "d"); - l.add(numberOfDaysCondition); - } - String propertyKey = (String) parentCondition.getParameter("generatedPropertyKey"); - Map<String, Long> res = persistenceService.aggregateQuery(andCondition, new TermsAggregate("profileId"), Event.ITEM_TYPE); - for (Map.Entry<String, Long> entry : res.entrySet()) { - if (!entry.getKey().startsWith("_")) { - Map<String,Object> p = new HashMap<>(); - p.put(propertyKey, entry.getValue()); - Map<String,Object> p2 = new HashMap<>(); - p2.put("pastEvents",p); - try { - persistenceService.update(entry.getKey(), null, Profile.class, "systemProperties", p2); - } catch (Exception e) { - logger.error(e.getMessage(), e); - } - } - } - - logger.info("Profiles past condition updated in {}", System.currentTimeMillis()-t); - } - - private void updateExistingProfilesForSegment(Segment segment) { - long t = System.currentTimeMillis(); - Condition segmentCondition = new Condition(); - - segmentCondition.setConditionType(definitionsService.getConditionType("profilePropertyCondition")); - segmentCondition.setParameter("propertyName", "segments"); - segmentCondition.setParameter("comparisonOperator", "equals"); - segmentCondition.setParameter("propertyValue", segment.getItemId()); - - if(segment.getMetadata().isEnabled()) { - List<Profile> previousProfiles = persistenceService.query(segmentCondition, null, Profile.class); - List<Profile> newProfiles = persistenceService.query(segment.getCondition(), null, Profile.class); - - List<Profile> add = new ArrayList<>(newProfiles); - add.removeAll(previousProfiles); - previousProfiles.removeAll(newProfiles); - - for (Profile profileToAdd : add) { - profileToAdd.getSegments().add(segment.getItemId()); - persistenceService.update(profileToAdd.getItemId(), null, Profile.class, "segments", profileToAdd.getSegments()); - } - for (Profile profileToRemove : previousProfiles) { - profileToRemove.getSegments().remove(segment.getItemId()); - persistenceService.update(profileToRemove.getItemId(), null, Profile.class, "segments", profileToRemove.getSegments()); - } - } else { - List<Profile> previousProfiles = persistenceService.query(segmentCondition, null, Profile.class); - for (Profile profileToRemove : previousProfiles) { - profileToRemove.getSegments().remove(segment.getItemId()); - persistenceService.update(profileToRemove.getItemId(), null, Profile.class, "segments", profileToRemove.getSegments()); - } - } - logger.info("Profiles updated in {}", System.currentTimeMillis()-t); - } - - private void updateExistingProfilesForScoring(Scoring scoring) { - long t = System.currentTimeMillis(); - Condition scoringCondition = new Condition(); - scoringCondition.setConditionType(definitionsService.getConditionType("profilePropertyCondition")); - scoringCondition.setParameter("propertyName", "scores." + scoring.getItemId()); - scoringCondition.setParameter("comparisonOperator", "exists"); - List<Profile> previousProfiles = persistenceService.query(scoringCondition, null, Profile.class); - - HashMap<String, Object> scriptParams = new HashMap<>(); - scriptParams.put("scoringId", scoring.getItemId()); - - for (Profile profileToRemove : previousProfiles) { - persistenceService.update(profileToRemove.getItemId(), null, Profile.class, "ctx._source.scores.remove(scoringId)", scriptParams); - } - if(scoring.getMetadata().isEnabled()) { - String script = "if (ctx._source.scores.containsKey(scoringId)) { ctx._source.scores[scoringId] += scoringValue } else { ctx._source.scores[scoringId] = scoringValue }"; - for (ScoringElement element : scoring.getElements()) { - scriptParams.put("scoringValue", element.getValue()); - for (Profile p : persistenceService.query(element.getCondition(), null, Profile.class)) { - persistenceService.update(p.getItemId(), null, Profile.class, script, scriptParams); - } - } - } - logger.info("Profiles updated in {}", System.currentTimeMillis()-t); - } - - private void updateExistingProfilesForRemovedScoring(String scoringId) { - long t = System.currentTimeMillis(); - Condition scoringCondition = new Condition(); - scoringCondition.setConditionType(definitionsService.getConditionType("profilePropertyCondition")); - scoringCondition.setParameter("propertyName", "scores." + scoringId); - scoringCondition.setParameter("comparisonOperator", "exists"); - List<Profile> previousProfiles = persistenceService.query(scoringCondition, null, Profile.class); - - HashMap<String, Object> scriptParams = new HashMap<>(); - scriptParams.put("scoringId", scoringId); - - for (Profile profileToRemove : previousProfiles) { - persistenceService.update(profileToRemove.getItemId(), null, Profile.class, "ctx._source.scores.remove(scoringId)", scriptParams); - } - logger.info("Profiles updated in {}", System.currentTimeMillis()-t); - } - - private String getMD5(String md5) { - try { - MessageDigest md = MessageDigest.getInstance("MD5"); - byte[] array = md.digest(md5.getBytes()); - StringBuffer sb = new StringBuffer(); - for (int i = 0; i < array.length; ++i) { - sb.append(Integer.toHexString((array[i] & 0xFF) | 0x100).substring(1, 3)); - } - return sb.toString(); - } catch (java.security.NoSuchAlgorithmException e) { - throw new RuntimeException(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; - } - } - - private void initializeTimer() { - segmentTimer = new Timer(); - TimerTask task = new TimerTask() { - @Override - public void run() { - for (Metadata metadata : rulesService.getRuleMetadatas()) { - Rule rule = rulesService.getRule(metadata.getId()); - for (Action action : rule.getActions()) { - if (action.getActionTypeId().equals("setEventOccurenceCountAction")) { - Condition pastEventCondition = (Condition) action.getParameterValues().get("pastEventCondition"); - if (pastEventCondition.containsParameter("numberOfDays")) { - updateExistingProfilesForPastEventCondition(rule.getCondition(), pastEventCondition); - } - } - } - } - } - }; - segmentTimer.scheduleAtFixedRate(task, getDay(1).getTime(), taskExecutionPeriod); - - task = new TimerTask() { - @Override - public void run() { - allSegments = getAllSegmentDefinitions(); - allScoring = getAllScoringDefinitions(); - } - }; - segmentTimer.scheduleAtFixedRate(task, 0, 1000); - } - - 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; - } - - public void setTaskExecutionPeriod(long taskExecutionPeriod) { - this.taskExecutionPeriod = taskExecutionPeriod; - } - - private <T extends MetadataItem> PartialList<Metadata> getMetadatas(int offset, int size, String sortBy, Class<T> clazz) { - PartialList<T> items = persistenceService.getAllItems(clazz, offset, size, sortBy); - List<Metadata> details = new LinkedList<>(); - for (T definition : items.getList()) { - details.add(definition.getMetadata()); - } - return new PartialList<>(details, items.getOffset(), items.getPageSize(), items.getTotalSize()); - } - - private <T extends MetadataItem> PartialList<Metadata> getMetadatas(Query query, Class<T> clazz) { - if(query.isForceRefresh()){ - persistenceService.refresh(); - } - definitionsService.resolveConditionType(query.getCondition()); - PartialList<T> items = persistenceService.query(query.getCondition(), query.getSortby(), clazz, query.getOffset(), query.getLimit()); - List<Metadata> details = new LinkedList<>(); - for (T definition : items.getList()) { - details.add(definition.getMetadata()); - } - return new PartialList<>(details, items.getOffset(), items.getPageSize(), items.getTotalSize()); - } - -}
http://git-wip-us.apache.org/repos/asf/incubator-unomi/blob/dc1d1520/services/src/main/resources/OSGI-INF/blueprint/blueprint.xml ---------------------------------------------------------------------- diff --git a/services/src/main/resources/OSGI-INF/blueprint/blueprint.xml b/services/src/main/resources/OSGI-INF/blueprint/blueprint.xml index 05d01ff..7d3bc87 100644 --- a/services/src/main/resources/OSGI-INF/blueprint/blueprint.xml +++ b/services/src/main/resources/OSGI-INF/blueprint/blueprint.xml @@ -16,8 +16,9 @@ ~ limitations under the License. --> -<blueprint xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xmlns="http://www.osgi.org/xmlns/blueprint/v1.0.0" +<blueprint xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xmlns:cm="http://aries.apache.org/blueprint/xmlns/blueprint-cm/v1.1.0" + xmlns="http://www.osgi.org/xmlns/blueprint/v1.0.0" xsi:schemaLocation="http://www.osgi.org/xmlns/blueprint/v1.0.0 http://www.osgi.org/xmlns/blueprint/v1.0.0/blueprint.xsd http://aries.apache.org/blueprint/xmlns/blueprint-cm/v1.1.0 http://aries.apache.org/schemas/blueprint-cm/blueprint-cm-1.1.0.xsd"> @@ -32,18 +33,18 @@ </cm:property-placeholder> <reference id="persistenceService" - interface="org.oasis_open.contextserver.persistence.spi.PersistenceService"/> + interface="org.apache.unomi.persistence.spi.PersistenceService"/> <reference id="httpService" interface="org.osgi.service.http.HttpService"/> <!-- Service definitions --> - <bean id="definitionsServiceImpl" class="org.oasis_open.contextserver.impl.services.DefinitionsServiceImpl" + <bean id="definitionsServiceImpl" class="org.apache.unomi.services.services.DefinitionsServiceImpl" init-method="postConstruct" destroy-method="preDestroy"> <property name="bundleContext" ref="blueprintBundleContext"/> </bean> <service id="definitionsService" ref="definitionsServiceImpl" auto-export="interfaces"/> - <bean id="eventServiceImpl" class="org.oasis_open.contextserver.impl.services.EventServiceImpl"> + <bean id="eventServiceImpl" class="org.apache.unomi.services.services.EventServiceImpl"> <property name="persistenceService" ref="persistenceService"/> <property name="profileService" ref="profileServiceImpl"/> <property name="definitionsService" ref="definitionsServiceImpl"/> @@ -60,7 +61,7 @@ </bean> <service id="eventService" ref="eventServiceImpl" auto-export="interfaces"/> - <bean id="goalsServiceImpl" class="org.oasis_open.contextserver.impl.services.GoalsServiceImpl" + <bean id="goalsServiceImpl" class="org.apache.unomi.services.services.GoalsServiceImpl" init-method="postConstruct" destroy-method="preDestroy"> <property name="persistenceService" ref="persistenceService"/> <property name="definitionsService" ref="definitionsServiceImpl"/> @@ -70,11 +71,11 @@ <service id="goalsService" ref="goalsServiceImpl" auto-export="interfaces"/> <bean id="actionExecutorDispatcherImpl" - class="org.oasis_open.contextserver.impl.actions.ActionExecutorDispatcher"> + class="org.apache.unomi.services.actions.ActionExecutorDispatcher"> <property name="bundleContext" ref="blueprintBundleContext"/> </bean> - <bean id="rulesServiceImpl" class="org.oasis_open.contextserver.impl.services.RulesServiceImpl" + <bean id="rulesServiceImpl" class="org.apache.unomi.services.services.RulesServiceImpl" init-method="postConstruct" destroy-method="preDestroy"> <property name="persistenceService" ref="persistenceService"/> <property name="definitionsService" ref="definitionsServiceImpl"/> @@ -84,7 +85,7 @@ </bean> <service id="rulesService" ref="rulesServiceImpl" auto-export="interfaces"/> - <bean id="segmentServiceImpl" class="org.oasis_open.contextserver.impl.services.SegmentServiceImpl" + <bean id="segmentServiceImpl" class="org.apache.unomi.services.services.SegmentServiceImpl" init-method="postConstruct" destroy-method="preDestroy"> <property name="persistenceService" ref="persistenceService"/> <property name="definitionsService" ref="definitionsServiceImpl"/> @@ -94,7 +95,7 @@ </bean> <service id="segmentService" ref="segmentServiceImpl" auto-export="interfaces"/> - <bean id="profileServiceImpl" class="org.oasis_open.contextserver.impl.services.ProfileServiceImpl" + <bean id="profileServiceImpl" class="org.apache.unomi.services.services.ProfileServiceImpl" init-method="postConstruct" destroy-method="preDestroy"> <property name="persistenceService" ref="persistenceService"/> <property name="definitionsService" ref="definitionsServiceImpl"/> @@ -107,7 +108,7 @@ </bean> <service id="profileService" ref="profileServiceImpl" auto-export="interfaces"/> - <bean id="queryServiceImpl" class="org.oasis_open.contextserver.impl.services.QueryServiceImpl" + <bean id="queryServiceImpl" class="org.apache.unomi.services.services.QueryServiceImpl" init-method="postConstruct" destroy-method="preDestroy"> <property name="persistenceService" ref="persistenceService"/> <property name="definitionsService" ref="definitionsServiceImpl"/> @@ -121,7 +122,7 @@ <service-properties> <entry key="initializerId" value="comparisonOperator"/> </service-properties> - <bean class="org.oasis_open.contextserver.impl.conditions.initializers.ComparisonOperatorChoiceListInitializer"> + <bean class="org.apache.unomi.services.conditions.initializers.ComparisonOperatorChoiceListInitializer"> <property name="bundleContext" ref="blueprintBundleContext"/> </bean> </service> @@ -130,7 +131,7 @@ <service-properties> <entry key="initializerId" value="eventProperty"/> </service-properties> - <bean class="org.oasis_open.contextserver.impl.conditions.initializers.EventPropertyChoiceListInitializer"> + <bean class="org.apache.unomi.services.conditions.initializers.EventPropertyChoiceListInitializer"> <property name="eventService" ref="eventServiceImpl"/> </bean> </service> @@ -139,7 +140,7 @@ <service-properties> <entry key="initializerId" value="segments"/> </service-properties> - <bean class="org.oasis_open.contextserver.impl.conditions.initializers.SegmentsChoiceListInitializer"> + <bean class="org.apache.unomi.services.conditions.initializers.SegmentsChoiceListInitializer"> <property name="segmentService" ref="segmentServiceImpl"/> </bean> </service> @@ -148,7 +149,7 @@ <service-properties> <entry key="initializerId" value="eventTypeId"/> </service-properties> - <bean class="org.oasis_open.contextserver.impl.conditions.initializers.EventTypeIdChoiceListInitializer"> + <bean class="org.apache.unomi.services.conditions.initializers.EventTypeIdChoiceListInitializer"> <property name="eventService" ref="eventServiceImpl"/> </bean> </service> @@ -157,14 +158,14 @@ <service-properties> <entry key="initializerId" value="country"/> </service-properties> - <bean class="org.oasis_open.contextserver.impl.conditions.initializers.CountryChoiceListInitializer"/> + <bean class="org.apache.unomi.services.conditions.initializers.CountryChoiceListInitializer"/> </service> <service auto-export="interfaces"> <service-properties> <entry key="initializerId" value="goals"/> </service-properties> - <bean class="org.oasis_open.contextserver.impl.conditions.initializers.GoalsChoiceListInitializer"> + <bean class="org.apache.unomi.services.conditions.initializers.GoalsChoiceListInitializer"> <property name="goalsService" ref="goalsServiceImpl"/> </bean> </service> @@ -172,7 +173,7 @@ <!-- We use a listener here because using the list directly for listening to proxies coming from the same bundle didn't seem to work --> <reference-list id="eventListenerServices" - interface="org.oasis_open.contextserver.api.services.EventListenerService" + interface="org.apache.unomi.api.services.EventListenerService" availability="optional"> <reference-listener bind-method="bind" unbind-method="unbind" ref="eventServiceImpl"/> @@ -184,7 +185,7 @@ <service-properties> <entry key="propertyMergeStrategyExecutorId" value="defaultPropertyMergeStrategyExecutor"/> </service-properties> - <bean class="org.oasis_open.contextserver.impl.mergers.DefaultPropertyMergeStrategyExecutor"> + <bean class="org.apache.unomi.services.mergers.DefaultPropertyMergeStrategyExecutor"> </bean> </service> @@ -192,7 +193,7 @@ <service-properties> <entry key="propertyMergeStrategyExecutorId" value="addPropertyMergeStrategyExecutor"/> </service-properties> - <bean class="org.oasis_open.contextserver.impl.mergers.AddPropertyMergeStrategyExecutor"> + <bean class="org.apache.unomi.services.mergers.AddPropertyMergeStrategyExecutor"> </bean> </service> @@ -200,7 +201,7 @@ <service-properties> <entry key="propertyMergeStrategyExecutorId" value="mostRecentPropertyMergeStrategyExecutor"/> </service-properties> - <bean class="org.oasis_open.contextserver.impl.mergers.MostRecentPropertyMergeStrategyExecutor"> + <bean class="org.apache.unomi.services.mergers.MostRecentPropertyMergeStrategyExecutor"> </bean> </service> @@ -208,7 +209,7 @@ <service-properties> <entry key="propertyMergeStrategyExecutorId" value="oldestPropertyMergeStrategyExecutor"/> </service-properties> - <bean class="org.oasis_open.contextserver.impl.mergers.OldestPropertyMergeStrategyExecutor"> + <bean class="org.apache.unomi.services.mergers.OldestPropertyMergeStrategyExecutor"> </bean> </service> @@ -216,7 +217,7 @@ <service-properties> <entry key="propertyMergeStrategyExecutorId" value="nonEmptyPropertyMergeStrategyExecutor"/> </service-properties> - <bean class="org.oasis_open.contextserver.impl.mergers.NonEmptyPropertyMergeStrategyExecutor"> + <bean class="org.apache.unomi.services.mergers.NonEmptyPropertyMergeStrategyExecutor"> </bean> </service> http://git-wip-us.apache.org/repos/asf/incubator-unomi/blob/dc1d1520/src/site/markdown/clustering.md ---------------------------------------------------------------------- diff --git a/src/site/markdown/clustering.md b/src/site/markdown/clustering.md index 5917cb5..7c51889 100644 --- a/src/site/markdown/clustering.md +++ b/src/site/markdown/clustering.md @@ -19,7 +19,7 @@ Cluster setup ============= Context server relies on Elasticsearch to discover and configure its cluster. You just need to install multiple context -servers on the same network, and enable the discovery protocol in $MY_KARAF_HOME/etc/org.oasis_open.contextserver.persistence.elasticsearch.cfg file : +servers on the same network, and enable the discovery protocol in $MY_KARAF_HOME/etc/org.apache.unomi.persistence.elasticsearch.cfg file : discovery.zen.ping.multicast.enabled=true @@ -68,7 +68,7 @@ Node C : ### Specific configuration If multicast is not allowed on your network, you'll need to switch to unicast protocol and manually configure the server IPs. This can be -done by disabling the elasticsearch automatic discovery in $MY_KARAF_HOME/etc/org.oasis_open.contextserver.persistence.elasticsearch.cfg : +done by disabling the elasticsearch automatic discovery in $MY_KARAF_HOME/etc/org.apache.unomi.persistence.elasticsearch.cfg : discovery.zen.ping.multicast.enabled=false http://git-wip-us.apache.org/repos/asf/incubator-unomi/blob/dc1d1520/src/site/markdown/configuration.md ---------------------------------------------------------------------- diff --git a/src/site/markdown/configuration.md b/src/site/markdown/configuration.md index 5059e75..5340d30 100644 --- a/src/site/markdown/configuration.md +++ b/src/site/markdown/configuration.md @@ -23,20 +23,21 @@ Changing the default configuration If you want to change the default configuration, you can perform any modification you want in the $MY_KARAF_HOME/etc directory. -The context server configuration is kept in the $MY_KARAF_HOME/etc/org.oasis_open.contextserver.web.cfg . It defines the +The context server configuration is kept in the $MY_KARAF_HOME/etc/org.apache.unomi.web.cfg . It defines the addresses and port where it can be found : contextserver.address=localhost contextserver.port=8181 contextserver.secureAddress=localhost contextserver.securePort=9443 + contextserver.domain=apache.org If you need to specify an Elasticsearch cluster name that is different than the default, it is recommended to do this BEFORE you start the server for the first time, or you will loose all the data you have stored previously. To change the cluster name, first create a file called - $MY_KARAF_HOME/etc/org.oasis_open.contextserver.persistence.elasticsearch.cfg + $MY_KARAF_HOME/etc/org.apache.unomi.persistence.elasticsearch.cfg with the following contents: @@ -71,7 +72,7 @@ In order to use it, you need to install the Geonames database into . Get the "al http://download.geonames.org/export/dump/ Download it and put it in the "etc" directory, without unzipping it. -Edit $MY_KARAF_HOME/etc/org.oasis_open.contextserver.geonames.cfg and set request.geonamesDatabase.forceImport to true, import should start right away. +Edit $MY_KARAF_HOME/etc/org.apache.unomi.geonames.cfg and set request.geonamesDatabase.forceImport to true, import should start right away. Otherwise, import should start at the next startup. Import runs in background, but can take about 15 minutes. At the end, you should have about 4 million entries in the geonames index. @@ -186,3 +187,87 @@ Step 4 : Setup a proxy in front of the context server As an alternative to an application-level firewall, you could also route all traffic to the context server through a proxy, and use it to filter any communication. + +Integrating with an Apache HTTP web server +------------------------------------------ + +If you want to setup an Apache HTTP web server in from of Apache Unomi, here is an example configuration using +mod_proxy. + +In your Unomi package directory, in /etc/org.apache.unomi.web.cfg for unomi.apache.org + + contextserver.address=unomi.apache.org + contextserver.port=80 + contextserver.secureAddress=unomi.apache.org + contextserver.securePort=443 + contextserver.domain=apache.org + +Main virtual host config: + + <VirtualHost *:80> + Include /var/www/vhosts/unomi.apache.org/conf/common.conf + </VirtualHost> + + <IfModule mod_ssl.c> + <VirtualHost *:443> + Include /var/www/vhosts/unomi.apache.org/conf/common.conf + + SSLEngine on + + SSLCertificateFile /var/www/vhosts/unomi.apache.org/conf/ssl/24d5b9691e96eafa.crt + SSLCertificateKeyFile /var/www/vhosts/unomi.apache.org/conf/ssl/apache.org.key + SSLCertificateChainFile /var/www/vhosts/unomi.apache.org/conf/ssl/gd_bundle-g2-g1.crt + + + <FilesMatch "\.(cgi|shtml|phtml|php)$"> + SSLOptions +StdEnvVars + </FilesMatch> + <Directory /usr/lib/cgi-bin> + SSLOptions +StdEnvVars + </Directory> + BrowserMatch "MSIE [2-6]" \ + nokeepalive ssl-unclean-shutdown \ + downgrade-1.0 force-response-1.0 + BrowserMatch "MSIE [17-9]" ssl-unclean-shutdown + + </VirtualHost> + </IfModule> + +common.conf: + + ServerName unomi.apache.org + ServerAdmin [email protected] + + DocumentRoot /var/www/vhosts/unomi.apache.org/html + CustomLog /var/log/apache2/access-unomi.apache.org.log combined + <Directory /> + Options FollowSymLinks + AllowOverride None + </Directory> + <Directory /var/www/vhosts/unomi.apache.org/html> + Options FollowSymLinks MultiViews + AllowOverride None + Order allow,deny + allow from all + </Directory> + <Location /cxs> + Order deny,allow + deny from all + allow from 88.198.26.2 + allow from www.apache.org + </Location> + + RewriteEngine On + RewriteCond %{REQUEST_METHOD} ^(TRACE|TRACK) + RewriteRule .* - [F] + ProxyPreserveHost On + ProxyPass /server-status ! + ProxyPass /robots.txt ! + + RewriteCond %{HTTP_USER_AGENT} Googlebot [OR] + RewriteCond %{HTTP_USER_AGENT} msnbot [OR] + RewriteCond %{HTTP_USER_AGENT} Slurp + RewriteRule ^.* - [F,L] + + ProxyPass / http://localhost:8181/ connectiontimeout=20 timeout=300 ttl=120 + ProxyPassReverse / http://localhost:8181/ \ No newline at end of file http://git-wip-us.apache.org/repos/asf/incubator-unomi/blob/dc1d1520/wab/pom.xml ---------------------------------------------------------------------- diff --git a/wab/pom.xml b/wab/pom.xml index dabfb0f..d054b78 100644 --- a/wab/pom.xml +++ b/wab/pom.xml @@ -15,7 +15,8 @@ ~ limitations under the License. --> -<project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd"> +<project xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xmlns="http://maven.apache.org/POM/4.0.0" + xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd"> <modelVersion>4.0.0</modelVersion> <parent> @@ -121,11 +122,11 @@ <Embed-Dependency>*;scope=compile|runtime</Embed-Dependency> <Embed-Directory>WEB-INF/lib</Embed-Directory> <Import-Package> - org.oasis_open.contextserver.api, - org.oasis_open.contextserver.api.conditions, - org.oasis_open.contextserver.api.segments, - org.oasis_open.contextserver.api.rules, - org.oasis_open.contextserver.api.query, + org.apache.unomi.api, + org.apache.unomi.api.conditions, + org.apache.unomi.api.segments, + org.apache.unomi.api.rules, + org.apache.unomi.api.query, * </Import-Package> <Web-ContextPath>/</Web-ContextPath> http://git-wip-us.apache.org/repos/asf/incubator-unomi/blob/dc1d1520/wab/src/main/java/org/apache/unomi/web/ContextServlet.java ---------------------------------------------------------------------- diff --git a/wab/src/main/java/org/apache/unomi/web/ContextServlet.java b/wab/src/main/java/org/apache/unomi/web/ContextServlet.java new file mode 100644 index 0000000..2e43302 --- /dev/null +++ b/wab/src/main/java/org/apache/unomi/web/ContextServlet.java @@ -0,0 +1,368 @@ +/* + * 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.web; + +import com.fasterxml.jackson.core.JsonFactory; +import com.fasterxml.jackson.databind.ObjectMapper; +import org.apache.commons.io.IOUtils; +import org.apache.unomi.api.*; +import org.apache.unomi.api.conditions.Condition; +import org.apache.unomi.api.services.EventService; +import org.apache.unomi.api.services.ProfileService; +import org.apache.unomi.api.services.RulesService; +import org.apache.unomi.persistence.spi.CustomObjectMapper; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; + +import javax.servlet.ServletException; +import javax.servlet.ServletRequest; +import javax.servlet.ServletResponse; +import javax.servlet.http.Cookie; +import javax.servlet.http.HttpServlet; +import javax.servlet.http.HttpServletRequest; +import javax.servlet.http.HttpServletResponse; +import java.io.IOException; +import java.io.InputStream; +import java.io.Writer; +import java.util.*; + +/** + * A servlet filter to serve a context-specific Javascript containing the current request context object. + */ +public class ContextServlet extends HttpServlet { + public static final String BASE_SCRIPT_LOCATION = "/javascript/base.js"; + public static final String IMPERSONATE_BASE_SCRIPT_LOCATION = "/javascript/impersonateBase.js"; + public static final String PROFILE_OVERRIDE_MARKER = "---IGNORE---"; + private static final Logger logger = LoggerFactory.getLogger(ContextServlet.class.getName()); + private static final long serialVersionUID = 2928875830103325238L; + private ProfileService profileService; + private EventService eventService; + private RulesService rulesService; + + private String profileIdCookieName = "context-profile-id"; + private String profileIdCookieDomain; +// private String personaIdCookieName = "context-persona-id"; + + + @Override + public void service(ServletRequest request, ServletResponse response) throws ServletException, IOException { + final Date timestamp = new Date(); + if (request.getParameter("timestamp") != null) { + timestamp.setTime(Long.parseLong(request.getParameter("timestamp"))); + } + // first we must retrieve the context for the current visitor, and build a Javascript object to attach to the + // script output. + String profileId; + + HttpServletRequest httpServletRequest = (HttpServletRequest) request; + String httpMethod = httpServletRequest.getMethod(); +// logger.debug(HttpUtils.dumpRequestInfo(httpServletRequest)); + + // set up CORS headers as soon as possible so that errors are not misconstrued on the client for CORS errors + HttpUtils.setupCORSHeaders(httpServletRequest, response); + + if ("options".equals(httpMethod.toLowerCase())) { + response.flushBuffer(); + return; + } + + Profile profile = null; + + String cookieProfileId = null; + String cookiePersonaId = null; + Cookie[] cookies = httpServletRequest.getCookies(); + for (Cookie cookie : cookies) { + if (profileIdCookieName.equals(cookie.getName())) { + cookieProfileId = cookie.getValue(); + } + } + + Session session = null; + + String personaId = request.getParameter("personaId"); + if (personaId != null) { + PersonaWithSessions personaWithSessions = profileService.loadPersonaWithSessions(personaId); + if (personaWithSessions == null) { + logger.error("Couldn't find persona with id=" + personaId); + profile = null; + } else { + profile = personaWithSessions.getPersona(); + session = personaWithSessions.getLastSession(); + } + } + + String sessionId = request.getParameter("sessionId"); + + if (cookieProfileId == null && sessionId == null && personaId == null) { + ((HttpServletResponse)response).sendError(HttpServletResponse.SC_BAD_REQUEST); + return; + } + + boolean profileCreated = false; + + ContextRequest contextRequest = null; + String scope = null; + String stringPayload = HttpUtils.getPayload(httpServletRequest); + if (stringPayload != null) { + ObjectMapper mapper = CustomObjectMapper.getObjectMapper(); + JsonFactory factory = mapper.getFactory(); + try { + contextRequest = mapper.readValue(factory.createParser(stringPayload), ContextRequest.class); + } catch (Exception e) { + logger.error("Cannot read payload " + stringPayload, e); + return; + } + scope = contextRequest.getSource().getScope(); + } + + int changes = EventService.NO_CHANGE; + + if (profile == null) { + if (sessionId != null) { + session = profileService.loadSession(sessionId, timestamp); + if (session != null) { + profileId = session.getProfileId(); + profile = profileService.load(profileId); + profile = checkMergedProfile(response, profile, session); + } + } + if (profile == null) { + // profile not stored in session + if (cookieProfileId == null) { + // no profileId cookie was found, we generate a new one and create the profile in the profile service + profile = createNewProfile(null, response, timestamp); + profileCreated = true; + } else { + profile = profileService.load(cookieProfileId); + if (profile == null) { + // this can happen if we have an old cookie but have reset the server, + // or if we merged the profiles and somehow this cookie didn't get updated. + profile = createNewProfile(null, response, timestamp); + profileCreated = true; + HttpUtils.sendProfileCookie(profile, response, profileIdCookieName, profileIdCookieDomain); + } else { + profile = checkMergedProfile(response, profile, session); + } + } + + } else if (cookieProfileId == null || !cookieProfileId.equals(profile.getItemId())) { + // profile if stored in session but not in cookie + HttpUtils.sendProfileCookie(profile, response, profileIdCookieName, profileIdCookieDomain); + } + // associate profile with session + if (sessionId != null && session == null) { + session = new Session(sessionId, profile, timestamp, scope); + changes |= EventService.SESSION_UPDATED; + Event event = new Event("sessionCreated", session, profile, scope, null, session, timestamp); + + event.getAttributes().put(Event.HTTP_REQUEST_ATTRIBUTE, request); + event.getAttributes().put(Event.HTTP_RESPONSE_ATTRIBUTE, response); + logger.debug("Received event " + event.getEventType() + " for profile=" + profile.getItemId() + " session=" + session.getItemId() + " target=" + event.getTarget() + " timestamp=" + timestamp); + changes |= eventService.send(event); + } + } + + if (profileCreated) { + changes |= EventService.PROFILE_UPDATED; + + Event profileUpdated = new Event("profileUpdated", session, profile, scope, null, profile, timestamp); + profileUpdated.setPersistent(false); + profileUpdated.getAttributes().put(Event.HTTP_REQUEST_ATTRIBUTE, request); + profileUpdated.getAttributes().put(Event.HTTP_RESPONSE_ATTRIBUTE, response); + + logger.debug("Received event {} for profile={} {} target={} timestamp={}", profileUpdated.getEventType(), profile.getItemId(), + session != null ? " session=" + session.getItemId() : "", profileUpdated.getTarget(), timestamp); + changes |= eventService.send(profileUpdated); + } + + ContextResponse data = new ContextResponse(); + + if(contextRequest != null){ + changes |= handleRequest(contextRequest, profile, session, data, request, response, timestamp); + } + + if ((changes & EventService.PROFILE_UPDATED) == EventService.PROFILE_UPDATED && profile != null) { + profileService.save(profile); + } + if ((changes & EventService.SESSION_UPDATED) == EventService.SESSION_UPDATED && session != null) { + profileService.saveSession(session); + } + + + String extension = httpServletRequest.getRequestURI().substring(httpServletRequest.getRequestURI().lastIndexOf(".") + 1); + boolean noScript = "json".equals(extension); + String contextAsJSONString = CustomObjectMapper.getObjectMapper().writeValueAsString(data); + Writer responseWriter; + if(noScript){ + response.setCharacterEncoding("UTF-8"); + responseWriter = response.getWriter(); + response.setContentType("application/json"); + IOUtils.write(contextAsJSONString, responseWriter); + }else { + responseWriter = response.getWriter(); + responseWriter.append("window.digitalData = window.digitalData || {};\n") + .append("var cxs = ") + .append(contextAsJSONString) + .append(";\n"); + + // now we copy the base script source code + InputStream baseScriptStream = getServletContext().getResourceAsStream(profile instanceof Persona ? IMPERSONATE_BASE_SCRIPT_LOCATION : BASE_SCRIPT_LOCATION); + IOUtils.copy(baseScriptStream, responseWriter); + } + + responseWriter.flush(); + } + + private Profile checkMergedProfile(ServletResponse response, Profile profile, Session session) { + String profileId; + if (profile != null && profile.getMergedWith() != null) { + profileId = profile.getMergedWith(); + Profile profileToDelete = profile; + profile = profileService.load(profileId); + if (profile != null) { + logger.debug("Session profile was merged with profile " + profileId + ", replacing profile in session"); + if (session != null) { + session.setProfile(profile); + profileService.saveSession(session); + } + HttpUtils.sendProfileCookie(profile, response, profileIdCookieName, profileIdCookieDomain); + } else { + logger.warn("Couldn't find merged profile" + profileId + ", falling back to profile " + profileToDelete.getItemId()); + profile = profileToDelete; + profile.setMergedWith(null); + profileService.save(profile); + } + } + return profile; + } + + private int handleRequest(ContextRequest contextRequest, Profile profile, Session session, ContextResponse data, ServletRequest request, ServletResponse response, Date timestamp) + throws IOException { + int changes = EventService.NO_CHANGE; + // execute provided events if any + if(contextRequest.getEvents() != null && !(profile instanceof Persona)) { + for (Event event : contextRequest.getEvents()){ + if(event.getEventType() != null) { + Event eventToSend; + if(event.getProperties() != null){ + eventToSend = new Event(event.getEventType(), session, profile, contextRequest.getSource().getScope(), event.getSource(), event.getTarget(), event.getProperties(), timestamp); + } else { + eventToSend = new Event(event.getEventType(), session, profile, contextRequest.getSource().getScope(), event.getSource(), event.getTarget(), timestamp); + } + event.getAttributes().put(Event.HTTP_REQUEST_ATTRIBUTE, request); + event.getAttributes().put(Event.HTTP_RESPONSE_ATTRIBUTE, response); + logger.debug("Received event " + event.getEventType() + " for profile=" + profile.getItemId() + " session=" + session.getItemId() + " target=" + event.getTarget() + " timestamp=" + timestamp); + changes |= eventService.send(eventToSend); + } + } + } + + data.setProfileId(profile.getItemId()); + + if (contextRequest.isRequireSegments()) { + data.setProfileSegments(profile.getSegments()); + } + + if (contextRequest.getRequiredProfileProperties() != null) { + Map<String, Object> profileProperties = new HashMap<String, Object>(profile.getProperties()); + if (!contextRequest.getRequiredProfileProperties().contains("*")) { + profileProperties.keySet().retainAll(contextRequest.getRequiredProfileProperties()); + } + data.setProfileProperties(profileProperties); + } + if (session != null) { + data.setSessionId(session.getItemId()); + if (contextRequest.getRequiredSessionProperties() != null) { + Map<String, Object> sessionProperties = new HashMap<String, Object>(session.getProperties()); + if (!contextRequest.getRequiredSessionProperties().contains("*")) { + sessionProperties.keySet().retainAll(contextRequest.getRequiredSessionProperties()); + } + data.setSessionProperties(sessionProperties); + } + } + + processOverrides(contextRequest, profile, session); + + List<ContextRequest.FilteredContent> filterNodes = contextRequest.getFilters(); + if (filterNodes != null) { + data.setFilteringResults(new HashMap<String, Boolean>()); + for (ContextRequest.FilteredContent filteredContent : filterNodes) { + boolean result = true; + for (ContextRequest.Filter filter : filteredContent.getFilters()) { + Condition condition = filter.getCondition(); + result &= profileService.matchCondition(condition, profile, session); + } + data.getFilteringResults().put(filteredContent.getFilterid(), result); + } + } + + if(!(profile instanceof Persona)) { + data.setTrackedConditions(rulesService.getTrackedConditions(contextRequest.getSource())); + } else { + data.setTrackedConditions(Collections.<Condition>emptySet()); + } + + return changes; + } + + private void processOverrides(ContextRequest contextRequest, Profile profile, Session session) { + if (contextRequest.getSegmentOverrides() != null) { + profile.setSegments(contextRequest.getSegmentOverrides()); + } + + if (contextRequest.getProfilePropertiesOverrides() != null) { + profile.setProperties(contextRequest.getProfilePropertiesOverrides()); + } + + if (contextRequest.getSessionPropertiesOverrides() != null) { + session.setProperties(contextRequest.getSessionPropertiesOverrides()); // we do this just in case a cache is behind this + } + } + + private Profile createNewProfile(String existingProfileId, ServletResponse response, Date timestamp) { + Profile profile; + String profileId = existingProfileId; + if (profileId == null) { + profileId = UUID.randomUUID().toString(); + } + profile = new Profile(profileId); + profile.setProperty("firstVisit", timestamp); + HttpUtils.sendProfileCookie(profile, response, profileIdCookieName, profileIdCookieDomain); + return profile; + } + + + public void destroy() { + } + + public void setProfileService(ProfileService profileService) { + this.profileService = profileService; + } + + public void setEventService(EventService eventService) { + this.eventService = eventService; + } + + public void setRulesService(RulesService rulesService) { + this.rulesService = rulesService; + } + + public void setProfileIdCookieDomain(String profileIdCookieDomain) { + this.profileIdCookieDomain = profileIdCookieDomain; + } +} http://git-wip-us.apache.org/repos/asf/incubator-unomi/blob/dc1d1520/wab/src/main/java/org/apache/unomi/web/EventsCollectorServlet.java ---------------------------------------------------------------------- diff --git a/wab/src/main/java/org/apache/unomi/web/EventsCollectorServlet.java b/wab/src/main/java/org/apache/unomi/web/EventsCollectorServlet.java new file mode 100644 index 0000000..c34ead2 --- /dev/null +++ b/wab/src/main/java/org/apache/unomi/web/EventsCollectorServlet.java @@ -0,0 +1,185 @@ +/* + * 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.web; + +import com.fasterxml.jackson.core.JsonFactory; +import com.fasterxml.jackson.databind.ObjectMapper; +import org.apache.unomi.api.*; +import org.apache.unomi.api.services.EventService; +import org.apache.unomi.api.services.PrivacyService; +import org.apache.unomi.api.services.ProfileService; +import org.apache.unomi.persistence.spi.CustomObjectMapper; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; + +import javax.servlet.ServletException; +import javax.servlet.http.HttpServlet; +import javax.servlet.http.HttpServletRequest; +import javax.servlet.http.HttpServletResponse; +import java.io.IOException; +import java.io.PrintWriter; +import java.util.Date; +import java.util.List; + +public class EventsCollectorServlet extends HttpServlet { + private static final Logger logger = LoggerFactory.getLogger(EventsCollectorServlet.class.getName()); + + private static final long serialVersionUID = 2008054804885122957L; + + private EventService eventService; + private ProfileService profileService; + private PrivacyService privacyService; + + @Override + protected void doGet(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException { + + doEvent(req, resp); + } + + @Override + protected void doPost(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException { + + doEvent(req, resp); + } + + @Override + protected void doOptions(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException { +// logger.debug(HttpUtils.dumpRequestInfo(request)); + HttpUtils.setupCORSHeaders(request, response); + response.flushBuffer(); + } + + private void doEvent(HttpServletRequest request, HttpServletResponse response) throws IOException { + Date timestamp = new Date(); + if (request.getParameter("timestamp") != null) { + timestamp.setTime(Long.parseLong(request.getParameter("timestamp"))); + } + +// logger.debug(HttpUtils.dumpRequestInfo(request)); + + HttpUtils.setupCORSHeaders(request, response); + + Profile profile = null; + + String sessionId = request.getParameter("sessionId"); + if (sessionId == null) { + return; + } + + Session session = profileService.loadSession(sessionId, timestamp); + if (session == null) { + return; + } + + String profileId = session.getProfileId(); + if (profileId == null) { + return; + } + + profile = profileService.load(profileId); + if (profile == null || profile instanceof Persona) { + return; + } + + String payload = HttpUtils.getPayload(request); + if(payload == null){ + return; + } + + Profile realProfile = profile; + Boolean profileIsAnonymous = privacyService.isAnonymous(profile.getItemId()); + if (profileIsAnonymous != null && profileIsAnonymous.booleanValue()) { + // we are surfing anonymously, we must use the global anonymous profile if it exists, or create it if + // it doesn't. + Profile anonymousProfile = profileService.load(PrivacyService.GLOBAL_ANONYMOUS_PROFILE_ID); + if (anonymousProfile == null) { + anonymousProfile = new Profile(PrivacyService.GLOBAL_ANONYMOUS_PROFILE_ID); + profileService.save(profile); + } + realProfile = profile; + profile = anonymousProfile; + } + + List<String> filteredEventTypes = privacyService.getFilteredEventTypes(profile.getItemId()); + + ObjectMapper mapper = CustomObjectMapper.getObjectMapper(); + JsonFactory factory = mapper.getFactory(); + EventsCollectorRequest events = null; + try { + events = mapper.readValue(factory.createParser(payload), EventsCollectorRequest.class); + } catch (Exception e) { + logger.error("Cannot read payload " + payload,e); + return; + } + if (events == null || events.getEvents() == null) { + return; + } + + int changes = 0; + for (Event event : events.getEvents()){ + if(event.getEventType() != null){ + Event eventToSend; + if(event.getProperties() != null){ + eventToSend = new Event(event.getEventType(), session, profile, event.getScope(), event.getSource(), event.getTarget(), event.getProperties(), timestamp); + } else { + eventToSend = new Event(event.getEventType(), session, profile, event.getScope(), event.getSource(), event.getTarget(), timestamp); + } + + if (filteredEventTypes != null && filteredEventTypes.contains(event.getEventType())) { + logger.debug("Profile is filtering event type {}", event.getEventType()); + continue; + } + + eventToSend.getAttributes().put(Event.HTTP_REQUEST_ATTRIBUTE, request); + eventToSend.getAttributes().put(Event.HTTP_RESPONSE_ATTRIBUTE, response); + logger.debug("Received event " + event.getEventType() + " for profile=" + profile.getItemId() + " session=" + session.getItemId() + " target=" + event.getTarget() + " timestamp=" + timestamp); + int eventChanged = eventService.send(eventToSend); + //if the event execution changes the profile + if ((eventChanged & EventService.PROFILE_UPDATED) == EventService.PROFILE_UPDATED) { + profile = eventToSend.getProfile(); + session.setProfile(profile); + } + changes |= eventChanged; + } + } + + if ((changes & EventService.PROFILE_UPDATED) == EventService.PROFILE_UPDATED) { + profileService.save(profile); + } + if ((changes & EventService.SESSION_UPDATED) == EventService.SESSION_UPDATED) { + profileService.saveSession(session); + } + + + PrintWriter responseWriter = response.getWriter(); + responseWriter.append("{\"updated\":" + changes + "}"); + responseWriter.flush(); + } + + public void setEventService(EventService eventService) { + this.eventService = eventService; + } + + public void setProfileService(ProfileService profileService) { + this.profileService = profileService; + } + + public void setPrivacyService(PrivacyService privacyService) { + this.privacyService = privacyService; + } +}
