UNOMI-202 : Added patch service , command and rest endpoint
Project: http://git-wip-us.apache.org/repos/asf/incubator-unomi/repo Commit: http://git-wip-us.apache.org/repos/asf/incubator-unomi/commit/a65f28f8 Tree: http://git-wip-us.apache.org/repos/asf/incubator-unomi/tree/a65f28f8 Diff: http://git-wip-us.apache.org/repos/asf/incubator-unomi/diff/a65f28f8 Branch: refs/heads/UNOMI-204 Commit: a65f28f83b39d5d7707a4b4d5fe39d13287a1c8f Parents: 10e694e Author: tdraier <dra...@apache.org> Authored: Wed Sep 26 14:57:56 2018 +0200 Committer: tdraier <dra...@apache.org> Committed: Mon Oct 1 16:23:43 2018 +0200 ---------------------------------------------------------------------- .../main/java/org/apache/unomi/api/Patch.java | 91 ++++++ .../apache/unomi/api/services/PatchService.java | 34 +++ kar/src/main/feature/feature.xml | 5 + .../apache/unomi/rest/PatchServiceEndPoint.java | 60 ++++ .../resources/OSGI-INF/blueprint/blueprint.xml | 17 ++ services/pom.xml | 7 + .../services/DefinitionsServiceImpl.java | 66 +++-- .../services/services/GoalsServiceImpl.java | 73 +++-- .../services/services/PatchServiceImpl.java | 116 ++++++++ .../services/services/ProfileServiceImpl.java | 95 +++--- .../services/services/RulesServiceImpl.java | 48 +-- .../services/services/SegmentServiceImpl.java | 79 +++-- .../resources/OSGI-INF/blueprint/blueprint.xml | 16 + .../unomi/shell/commands/DeployDefinition.java | 221 -------------- .../shell/commands/DeployDefinitionCommand.java | 297 +++++++++++++++++++ .../resources/OSGI-INF/blueprint/blueprint.xml | 4 +- 16 files changed, 864 insertions(+), 365 deletions(-) ---------------------------------------------------------------------- http://git-wip-us.apache.org/repos/asf/incubator-unomi/blob/a65f28f8/api/src/main/java/org/apache/unomi/api/Patch.java ---------------------------------------------------------------------- diff --git a/api/src/main/java/org/apache/unomi/api/Patch.java b/api/src/main/java/org/apache/unomi/api/Patch.java new file mode 100644 index 0000000..9380ef8 --- /dev/null +++ b/api/src/main/java/org/apache/unomi/api/Patch.java @@ -0,0 +1,91 @@ +/* + * 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.api; + +import org.apache.unomi.api.actions.ActionType; +import org.apache.unomi.api.campaigns.Campaign; +import org.apache.unomi.api.conditions.ConditionType; +import org.apache.unomi.api.goals.Goal; +import org.apache.unomi.api.rules.Rule; +import org.apache.unomi.api.segments.Scoring; +import org.apache.unomi.api.segments.Segment; + +import java.util.Date; +import java.util.Map; +import java.util.TreeMap; + +public class Patch extends Item { + + public final static Map<String, Class<? extends Item>> PATCHABLE_TYPES; + + static { + PATCHABLE_TYPES = new TreeMap<>(); + PATCHABLE_TYPES.put("condition", ConditionType.class); + PATCHABLE_TYPES.put("action", ActionType.class); + PATCHABLE_TYPES.put("goal", Goal.class); + PATCHABLE_TYPES.put("campaign", Campaign.class); + PATCHABLE_TYPES.put("persona",Persona.class); + PATCHABLE_TYPES.put("property",PropertyType.class); + PATCHABLE_TYPES.put("rule", Rule.class); + PATCHABLE_TYPES.put("segment", Segment.class); + PATCHABLE_TYPES.put("scoring", Scoring.class); + } + + public static final String ITEM_TYPE = "patch"; + + private String patchedItemId; + + private String operation; + + private Object data; + + private Date lastApplication; + + public String getPatchedItemId() { + return patchedItemId; + } + + public void setPatchedItemId(String patchedItemId) { + this.patchedItemId = patchedItemId; + } + + public String getOperation() { + return operation; + } + + public void setOperation(String operation) { + this.operation = operation; + } + + + public Object getData() { + return data; + } + + public void setData(Object data) { + this.data = data; + } + + public Date getLastApplication() { + return lastApplication; + } + + public void setLastApplication(Date lastApplication) { + this.lastApplication = lastApplication; + } +} http://git-wip-us.apache.org/repos/asf/incubator-unomi/blob/a65f28f8/api/src/main/java/org/apache/unomi/api/services/PatchService.java ---------------------------------------------------------------------- diff --git a/api/src/main/java/org/apache/unomi/api/services/PatchService.java b/api/src/main/java/org/apache/unomi/api/services/PatchService.java new file mode 100644 index 0000000..47f53ca --- /dev/null +++ b/api/src/main/java/org/apache/unomi/api/services/PatchService.java @@ -0,0 +1,34 @@ +/* + * 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.api.services; + +import org.apache.unomi.api.Item; +import org.apache.unomi.api.Patch; + +import java.net.URL; +import java.util.Enumeration; + +public interface PatchService { + Patch load(String id); + + void patch(Enumeration<URL> urls, Class<? extends Item> type); + + void patch(URL patchUrl, Class<? extends Item> type); + + <T extends Item> T patch(Patch patch, Class<T> type) ; +} + http://git-wip-us.apache.org/repos/asf/incubator-unomi/blob/a65f28f8/kar/src/main/feature/feature.xml ---------------------------------------------------------------------- diff --git a/kar/src/main/feature/feature.xml b/kar/src/main/feature/feature.xml index 012ad0d..4301c38 100644 --- a/kar/src/main/feature/feature.xml +++ b/kar/src/main/feature/feature.xml @@ -34,6 +34,11 @@ <configfile finalname="/etc/org.apache.unomi.geonames.cfg">mvn:org.apache.unomi/cxs-geonames-services/${project.version}/cfg/geonamescfg</configfile> <bundle start-level="75">mvn:commons-io/commons-io/2.4</bundle> <bundle start-level="75">mvn:com.fasterxml.jackson.core/jackson-core/${version.jackson.core}</bundle> + <bundle start-level="75">mvn:com.github.fge/btf/1.2</bundle> + <bundle start-level="75">mvn:com.github.fge/msg-simple/1.1</bundle> + <bundle start-level="75">mvn:com.google.guava/guava/16.0.1</bundle> + <bundle start-level="75">mvn:com.github.fge/jackson-coreutils/1.8</bundle> + <bundle start-level="75">mvn:com.github.fge/json-patch/1.9</bundle> <bundle start-level="75">mvn:com.fasterxml.jackson.core/jackson-databind/${version.jackson.core}</bundle> <bundle start-level="75">mvn:com.fasterxml.jackson.core/jackson-annotations/${version.jackson.core}</bundle> <bundle start-level="75">mvn:com.fasterxml.jackson.jaxrs/jackson-jaxrs-base/${version.jackson.core}</bundle> http://git-wip-us.apache.org/repos/asf/incubator-unomi/blob/a65f28f8/rest/src/main/java/org/apache/unomi/rest/PatchServiceEndPoint.java ---------------------------------------------------------------------- diff --git a/rest/src/main/java/org/apache/unomi/rest/PatchServiceEndPoint.java b/rest/src/main/java/org/apache/unomi/rest/PatchServiceEndPoint.java new file mode 100644 index 0000000..20f7cd8 --- /dev/null +++ b/rest/src/main/java/org/apache/unomi/rest/PatchServiceEndPoint.java @@ -0,0 +1,60 @@ +/* + * 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.rest; + +import org.apache.cxf.rs.security.cors.CrossOriginResourceSharing; +import org.apache.unomi.api.Patch; +import org.apache.unomi.api.services.PatchService; + +import javax.jws.WebService; +import javax.ws.rs.*; +import javax.ws.rs.core.MediaType; + +/** + * A JAX-RS endpoint to manage patches. + */ +@WebService +@Produces(MediaType.APPLICATION_JSON) +@Consumes(MediaType.APPLICATION_JSON) +@CrossOriginResourceSharing( + allowAllOrigins = true, + allowCredentials = true +) +public class PatchServiceEndPoint { + + private PatchService patchService; + + public void setPatchService(PatchService patchService) { + this.patchService = patchService; + } + + /** + * Apply a patch on an item + * + * @param type the type of item to patch + */ + @POST + @Path("/apply/{type}") + public void setPatch(Patch patch, @PathParam("type") String type, @QueryParam("force") Boolean force) { + Patch previous = (force == null || !force) ? patchService.load(patch.getItemId()) : null; + if (previous == null) { + patchService.patch(patch, Patch.PATCHABLE_TYPES.get(type)); + } + } + +} http://git-wip-us.apache.org/repos/asf/incubator-unomi/blob/a65f28f8/rest/src/main/resources/OSGI-INF/blueprint/blueprint.xml ---------------------------------------------------------------------- diff --git a/rest/src/main/resources/OSGI-INF/blueprint/blueprint.xml b/rest/src/main/resources/OSGI-INF/blueprint/blueprint.xml index 2741fcf..082bf7b 100644 --- a/rest/src/main/resources/OSGI-INF/blueprint/blueprint.xml +++ b/rest/src/main/resources/OSGI-INF/blueprint/blueprint.xml @@ -178,6 +178,18 @@ </jaxrs:serviceBeans> </jaxrs:server> + <jaxrs:server address="/patches" id="restPatchService"> + <jaxrs:providers> + <ref component-id="jaxb-provider"/> + <ref component-id="cors-filter"/> + <ref component-id="jaas-filter"/> + </jaxrs:providers> + + <jaxrs:serviceBeans> + <ref component-id="patchServiceEndPoint"/> + </jaxrs:serviceBeans> + </jaxrs:server> + <reference id="segmentService" interface="org.apache.unomi.api.services.SegmentService"/> <reference id="userListService" interface="org.apache.unomi.api.services.UserListService"/> <reference id="definitionsService" interface="org.apache.unomi.api.services.DefinitionsService"/> @@ -187,6 +199,7 @@ <reference id="clusterService" interface="org.apache.unomi.api.services.ClusterService"/> <reference id="queryService" interface="org.apache.unomi.api.services.QueryService"/> <reference id="eventService" interface="org.apache.unomi.api.services.EventService"/> + <reference id="patchService" interface="org.apache.unomi.api.services.PatchService"/> <bean id="segmentServiceEndPoint" class="org.apache.unomi.rest.SegmentServiceEndPoint"> <property name="segmentService" ref="segmentService"/> @@ -233,6 +246,10 @@ <property name="localizationHelper" ref="localizationHelper"/> </bean> + <bean id="patchServiceEndPoint" class="org.apache.unomi.rest.PatchServiceEndPoint"> + <property name="patchService" ref="patchService"/> + </bean> + <bean id="resourceBundleHelper" class="org.apache.unomi.rest.ResourceBundleHelper"> <property name="bundleContext" ref="blueprintBundleContext"/> </bean> http://git-wip-us.apache.org/repos/asf/incubator-unomi/blob/a65f28f8/services/pom.xml ---------------------------------------------------------------------- diff --git a/services/pom.xml b/services/pom.xml index ed07557..b6e4afb 100644 --- a/services/pom.xml +++ b/services/pom.xml @@ -80,6 +80,13 @@ </dependency> <dependency> + <groupId>com.github.fge</groupId> + <artifactId>json-patch</artifactId> + <version>1.9</version> + <scope>provided</scope> + </dependency> + + <dependency> <groupId>com.fasterxml.jackson.core</groupId> <artifactId>jackson-core</artifactId> <scope>provided</scope> http://git-wip-us.apache.org/repos/asf/incubator-unomi/blob/a65f28f8/services/src/main/java/org/apache/unomi/services/services/DefinitionsServiceImpl.java ---------------------------------------------------------------------- diff --git a/services/src/main/java/org/apache/unomi/services/services/DefinitionsServiceImpl.java b/services/src/main/java/org/apache/unomi/services/services/DefinitionsServiceImpl.java index eacd270..0daaf5b 100644 --- a/services/src/main/java/org/apache/unomi/services/services/DefinitionsServiceImpl.java +++ b/services/src/main/java/org/apache/unomi/services/services/DefinitionsServiceImpl.java @@ -17,6 +17,7 @@ package org.apache.unomi.services.services; +import org.apache.unomi.api.Persona; import org.apache.unomi.api.PluginType; import org.apache.unomi.api.PropertyMergeStrategyType; import org.apache.unomi.api.ValueType; @@ -24,6 +25,7 @@ import org.apache.unomi.api.actions.ActionType; import org.apache.unomi.api.conditions.Condition; import org.apache.unomi.api.conditions.ConditionType; import org.apache.unomi.api.services.DefinitionsService; +import org.apache.unomi.api.services.PatchService; import org.apache.unomi.persistence.spi.CustomObjectMapper; import org.apache.unomi.persistence.spi.PersistenceService; import org.apache.unomi.persistence.spi.aggregate.BaseAggregate; @@ -44,6 +46,7 @@ public class DefinitionsServiceImpl implements DefinitionsService, SynchronousBu private static final Logger logger = LoggerFactory.getLogger(DefinitionsServiceImpl.class.getName()); private PersistenceService persistenceService; + private PatchService patchService; private Map<String, ConditionType> conditionTypeById = new ConcurrentHashMap<>(); private Map<String, ActionType> actionTypeById = new ConcurrentHashMap<>(); @@ -65,6 +68,10 @@ public class DefinitionsServiceImpl implements DefinitionsService, SynchronousBu this.persistenceService = persistenceService; } + public void setPatchService(PatchService patchService) { + this.patchService = patchService; + } + public void postConstruct() { logger.debug("postConstruct {" + bundleContext.getBundle() + "}"); @@ -126,21 +133,26 @@ public class DefinitionsServiceImpl implements DefinitionsService, SynchronousBu return; } + // First apply patches on existing items + patchService.patch(bundleContext.getBundle().findEntries("META-INF/cxs/conditions", "*-patch.json", true), ConditionType.class); + while (predefinedConditionEntries.hasMoreElements()) { URL predefinedConditionURL = predefinedConditionEntries.nextElement(); - logger.debug("Found predefined condition at " + predefinedConditionURL + ", loading... "); - - try { - ConditionType conditionType = CustomObjectMapper.getObjectMapper().readValue(predefinedConditionURL, ConditionType.class); - // Register only if condition type does not exist yet - if (getConditionType(conditionType.getMetadata().getId()) == null || bundleContext.getBundle().getVersion().toString().contains("SNAPSHOT")) { - setConditionType(conditionType); - logger.info("Predefined condition type with id {} registered", conditionType.getMetadata().getId()); - } else { - logger.info("The predefined condition type with id {} is already registered, this condition type will be skipped", conditionType.getMetadata().getId()); + if (!predefinedConditionURL.getFile().endsWith("-patch.json")) { + logger.debug("Found predefined condition at " + predefinedConditionURL + ", loading... "); + + try { + ConditionType conditionType = CustomObjectMapper.getObjectMapper().readValue(predefinedConditionURL, ConditionType.class); + // Register only if condition type does not exist yet + if (getConditionType(conditionType.getMetadata().getId()) == null) { + setConditionType(conditionType); + logger.info("Predefined condition type with id {} registered", conditionType.getMetadata().getId()); + } else { + logger.info("The predefined condition type with id {} is already registered, this condition type will be skipped", conditionType.getMetadata().getId()); + } + } catch (IOException e) { + logger.error("Error while loading condition definition " + predefinedConditionURL, e); } - } catch (IOException e) { - logger.error("Error while loading condition definition " + predefinedConditionURL, e); } } } @@ -150,22 +162,28 @@ public class DefinitionsServiceImpl implements DefinitionsService, SynchronousBu if (predefinedActionsEntries == null) { return; } + + // First apply patches on existing items + patchService.patch(bundleContext.getBundle().findEntries("META-INF/cxs/actions", "*-patch.json", true), ActionType.class); + ArrayList<PluginType> pluginTypeArrayList = (ArrayList<PluginType>) pluginTypes.get(bundleContext.getBundle().getBundleId()); while (predefinedActionsEntries.hasMoreElements()) { URL predefinedActionURL = predefinedActionsEntries.nextElement(); - logger.debug("Found predefined action at " + predefinedActionURL + ", loading... "); - - try { - ActionType actionType = CustomObjectMapper.getObjectMapper().readValue(predefinedActionURL, ActionType.class); - // Register only if action type does not exist yet - if (getActionType(actionType.getMetadata().getId()) == null || bundleContext.getBundle().getVersion().toString().contains("SNAPSHOT")) { - setActionType(actionType); - logger.info("Predefined action type with id {} registered", actionType.getMetadata().getId()); - } else { - logger.info("The predefined action type with id {} is already registered, this action type will be skipped", actionType.getMetadata().getId()); + if (!predefinedActionURL.getFile().endsWith("-patch.json")) { + logger.debug("Found predefined action at " + predefinedActionURL + ", loading... "); + + try { + ActionType actionType = CustomObjectMapper.getObjectMapper().readValue(predefinedActionURL, ActionType.class); + // Register only if action type does not exist yet + if (getActionType(actionType.getMetadata().getId()) == null) { + setActionType(actionType); + logger.info("Predefined action type with id {} registered", actionType.getMetadata().getId()); + } else { + logger.info("The predefined action type with id {} is already registered, this action type will be skipped", actionType.getMetadata().getId()); + } + } catch (Exception e) { + logger.error("Error while loading action definition " + predefinedActionURL, e); } - } catch (Exception e) { - logger.error("Error while loading action definition " + predefinedActionURL, e); } } http://git-wip-us.apache.org/repos/asf/incubator-unomi/blob/a65f28f8/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 index 771e21f..0de6c64 100644 --- a/services/src/main/java/org/apache/unomi/services/services/GoalsServiceImpl.java +++ b/services/src/main/java/org/apache/unomi/services/services/GoalsServiceImpl.java @@ -30,6 +30,7 @@ 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.PatchService; import org.apache.unomi.api.services.RulesService; import org.apache.unomi.persistence.spi.CustomObjectMapper; import org.apache.unomi.persistence.spi.PersistenceService; @@ -57,6 +58,8 @@ public class GoalsServiceImpl implements GoalsService, SynchronousBundleListener private RulesService rulesService; + private PatchService patchService; + public void setBundleContext(BundleContext bundleContext) { this.bundleContext = bundleContext; } @@ -73,6 +76,10 @@ public class GoalsServiceImpl implements GoalsService, SynchronousBundleListener this.rulesService = rulesService; } + public void setPatchService(PatchService patchService) { + this.patchService = patchService; + } + public void postConstruct() { logger.debug("postConstruct {" + bundleContext.getBundle() + "}"); @@ -109,24 +116,30 @@ public class GoalsServiceImpl implements GoalsService, SynchronousBundleListener if (predefinedRuleEntries == null) { return; } + + // First apply patches on existing items + patchService.patch(bundleContext.getBundle().findEntries("META-INF/cxs/goals", "*-patch.json", true), Goal.class); + 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"); - } - // Register only if goal does not exist yet - if (getGoal(goal.getMetadata().getId()) == null || bundleContext.getBundle().getVersion().toString().contains("SNAPSHOT")) { - setGoal(goal); - logger.info("Predefined goal with id {} registered", goal.getMetadata().getId()); - } else { - logger.info("The predefined goal with id {} is already registered, this goal will be skipped", goal.getMetadata().getId()); + if (!predefinedGoalURL.getFile().endsWith("-patch.json")) { + 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"); + } + // Register only if goal does not exist yet + if (getGoal(goal.getMetadata().getId()) == null) { + setGoal(goal); + logger.info("Predefined goal with id {} registered", goal.getMetadata().getId()); + } else { + logger.info("The predefined goal with id {} is already registered, this goal will be skipped", goal.getMetadata().getId()); + } + } catch (IOException e) { + logger.error("Error while loading segment definition " + predefinedGoalURL, e); } - } catch (IOException e) { - logger.error("Error while loading segment definition " + predefinedGoalURL, e); } } } @@ -257,21 +270,27 @@ public class GoalsServiceImpl implements GoalsService, SynchronousBundleListener if (predefinedRuleEntries == null) { return; } + + // First apply patches on existing items + patchService.patch(bundleContext.getBundle().findEntries("META-INF/cxs/campaigns", "*-patch.json", true), Campaign.class); + while (predefinedRuleEntries.hasMoreElements()) { URL predefinedCampaignURL = predefinedRuleEntries.nextElement(); - logger.debug("Found predefined campaigns at " + predefinedCampaignURL + ", loading... "); - - try { - Campaign campaign = CustomObjectMapper.getObjectMapper().readValue(predefinedCampaignURL, Campaign.class); - // Register only if campaign does not exist yet - if (getCampaign(campaign.getMetadata().getId()) == null || bundleContext.getBundle().getVersion().toString().contains("SNAPSHOT")) { - setCampaign(campaign); - logger.info("Predefined campaign with id {} registered", campaign.getMetadata().getId()); - } else { - logger.info("The predefined campaign with id {} is already registered, this campaign will be skipped", campaign.getMetadata().getId()); + if (!predefinedCampaignURL.getFile().endsWith("-patch.json")) { + logger.debug("Found predefined campaigns at " + predefinedCampaignURL + ", loading... "); + + try { + Campaign campaign = CustomObjectMapper.getObjectMapper().readValue(predefinedCampaignURL, Campaign.class); + // Register only if campaign does not exist yet + if (getCampaign(campaign.getMetadata().getId()) == null) { + setCampaign(campaign); + logger.info("Predefined campaign with id {} registered", campaign.getMetadata().getId()); + } else { + logger.info("The predefined campaign with id {} is already registered, this campaign will be skipped", campaign.getMetadata().getId()); + } + } catch (IOException e) { + logger.error("Error while loading segment definition " + predefinedCampaignURL, e); } - } catch (IOException e) { - logger.error("Error while loading segment definition " + predefinedCampaignURL, e); } } } http://git-wip-us.apache.org/repos/asf/incubator-unomi/blob/a65f28f8/services/src/main/java/org/apache/unomi/services/services/PatchServiceImpl.java ---------------------------------------------------------------------- diff --git a/services/src/main/java/org/apache/unomi/services/services/PatchServiceImpl.java b/services/src/main/java/org/apache/unomi/services/services/PatchServiceImpl.java new file mode 100644 index 0000000..c6a8b77 --- /dev/null +++ b/services/src/main/java/org/apache/unomi/services/services/PatchServiceImpl.java @@ -0,0 +1,116 @@ +/* + * 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 com.fasterxml.jackson.databind.JsonNode; +import com.github.fge.jsonpatch.JsonPatch; +import com.github.fge.jsonpatch.JsonPatchException; +import org.apache.unomi.api.Item; +import org.apache.unomi.api.Patch; +import org.apache.unomi.api.PropertyType; +import org.apache.unomi.api.services.PatchService; +import org.apache.unomi.persistence.spi.CustomObjectMapper; +import org.apache.unomi.persistence.spi.PersistenceService; +import org.osgi.framework.BundleContext; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; + +import java.io.IOException; +import java.net.URL; +import java.util.Date; +import java.util.Enumeration; + +public class PatchServiceImpl implements PatchService { + + private static final Logger logger = LoggerFactory.getLogger(ProfileServiceImpl.class.getName()); + + private BundleContext bundleContext; + + private PersistenceService persistenceService; + + public void setBundleContext(BundleContext bundleContext) { + this.bundleContext = bundleContext; + } + + public void setPersistenceService(PersistenceService persistenceService) { + this.persistenceService = persistenceService; + } + + @Override + public Patch load(String id) { + return persistenceService.load(id, Patch.class); + } + + public void patch(Enumeration<URL> urls, Class<? extends Item> type) { + if (urls != null) { + while (urls.hasMoreElements()) { + patch(urls.nextElement(), type); + } + } + } + + public void patch(URL patchUrl, Class<? extends Item> type) { + try { + Patch patch = CustomObjectMapper.getObjectMapper().readValue(patchUrl, Patch.class); + if (persistenceService.load(patch.getItemId(), Patch.class) == null) { + patch(patch, type); + } + } catch (IOException e) { + logger.error("Error while loading patch " + patchUrl, e); + } + } + + public <T extends Item> T patch(Patch patch, Class<T> type) { + if (type == null) { + throw new IllegalArgumentException("Must specify valid type"); + } + + T item = persistenceService.load(patch.getPatchedItemId(), type); + + if (item != null && patch.getOperation() != null) { + logger.info("Applying patch " + patch.getItemId()); + + switch (patch.getOperation()) { + case "override": + item = CustomObjectMapper.getObjectMapper().convertValue(patch.getData(), type); + persistenceService.save(item); + break; + case "patch": + JsonNode node = CustomObjectMapper.getObjectMapper().valueToTree(item); + JsonPatch jsonPatch = CustomObjectMapper.getObjectMapper().convertValue(patch.getData(), JsonPatch.class); + try { + JsonNode converted = jsonPatch.apply(node); + item = CustomObjectMapper.getObjectMapper().convertValue(converted, type); + persistenceService.save(item); + } catch (JsonPatchException e) { + logger.error("Cannot apply patch",e); + } + break; + case "remove": + persistenceService.remove(patch.getPatchedItemId(), type); + break; + } + + } + + patch.setLastApplication(new Date()); + persistenceService.save(patch); + + return item; + } + +} http://git-wip-us.apache.org/repos/asf/incubator-unomi/blob/a65f28f8/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 index 02f6f0c..57ebf94 100644 --- a/services/src/main/java/org/apache/unomi/services/services/ProfileServiceImpl.java +++ b/services/src/main/java/org/apache/unomi/services/services/ProfileServiceImpl.java @@ -17,6 +17,9 @@ package org.apache.unomi.services.services; +import com.fasterxml.jackson.databind.JsonNode; +import com.github.fge.jsonpatch.JsonPatch; +import com.github.fge.jsonpatch.JsonPatchException; import org.apache.commons.beanutils.BeanUtils; import org.apache.commons.beanutils.PropertyUtils; import org.apache.commons.lang3.StringUtils; @@ -25,10 +28,7 @@ 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.segments.Segment; -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.api.services.SegmentService; +import org.apache.unomi.api.services.*; import org.apache.unomi.persistence.spi.CustomObjectMapper; import org.apache.unomi.persistence.spi.PersistenceService; import org.apache.unomi.persistence.spi.PropertyHelper; @@ -42,6 +42,8 @@ import java.util.*; import java.util.stream.Collectors; import java.util.stream.Stream; +import static org.apache.unomi.persistence.spi.CustomObjectMapper.getObjectMapper; + public class ProfileServiceImpl implements ProfileService, SynchronousBundleListener { /** @@ -160,6 +162,8 @@ public class ProfileServiceImpl implements ProfileService, SynchronousBundleList private QueryService queryService; + private PatchService patchService; + private Condition purgeProfileQuery; private Integer purgeProfileExistTime = 0; private Integer purgeProfileInactiveTime = 0; @@ -194,6 +198,14 @@ public class ProfileServiceImpl implements ProfileService, SynchronousBundleList this.segmentService = segmentService; } + public void setQueryService(QueryService queryService) { + this.queryService = queryService; + } + + public void setPatchService(PatchService patchService) { + this.patchService = patchService; + } + public void setForceRefreshOnSave(boolean forceRefreshOnSave) { this.forceRefreshOnSave = forceRefreshOnSave; } @@ -232,10 +244,6 @@ public class ProfileServiceImpl implements ProfileService, SynchronousBundleList private void processBundleStop(BundleContext bundleContext) { } - public void setQueryService(QueryService queryService) { - this.queryService = queryService; - } - public void setPurgeProfileExistTime(Integer purgeProfileExistTime) { this.purgeProfileExistTime = purgeProfileExistTime; } @@ -915,31 +923,35 @@ public class ProfileServiceImpl implements ProfileService, SynchronousBundleList return; } + // First apply patches on existing items + patchService.patch(bundleContext.getBundle().findEntries("META-INF/cxs/personas", "*-patch.json", true), Persona.class); + while (predefinedPersonaEntries.hasMoreElements()) { URL predefinedPersonaURL = predefinedPersonaEntries.nextElement(); - logger.debug("Found predefined persona at " + predefinedPersonaURL + ", loading... "); + if (!predefinedPersonaURL.getFile().endsWith("-patch.json")) { + logger.debug("Found predefined persona at " + predefinedPersonaURL + ", loading... "); - try { - PersonaWithSessions persona = CustomObjectMapper.getObjectMapper().readValue(predefinedPersonaURL, PersonaWithSessions.class); + try { + PersonaWithSessions persona = getObjectMapper().readValue(predefinedPersonaURL, PersonaWithSessions.class); - String itemId = persona.getPersona().getItemId(); - // Register only if persona does not exist yet - if (persistenceService.load(itemId, Persona.class) == null || bundleContext.getBundle().getVersion().toString().contains("SNAPSHOT")) { - persistenceService.save(persona.getPersona()); + String itemId = persona.getPersona().getItemId(); + // Register only if persona does not exist yet + if (persistenceService.load(itemId, Persona.class) == null) { + persistenceService.save(persona.getPersona()); - List<PersonaSession> sessions = persona.getSessions(); - for (PersonaSession session : sessions) { - session.setProfile(persona.getPersona()); - persistenceService.save(session); + List<PersonaSession> sessions = persona.getSessions(); + for (PersonaSession session : sessions) { + session.setProfile(persona.getPersona()); + persistenceService.save(session); + } + logger.info("Predefined persona with id {} registered", itemId); + } else { + logger.info("The predefined persona with id {} is already registered, this persona will be skipped", itemId); } - logger.info("Predefined persona with id {} registered", itemId); - } else { - logger.info("The predefined persona with id {} is already registered, this persona will be skipped", itemId); + } catch (IOException e) { + logger.error("Error while loading persona " + predefinedPersonaURL, e); } - } catch (IOException e) { - logger.error("Error while loading persona " + predefinedPersonaURL, e); } - } } @@ -949,31 +961,38 @@ public class ProfileServiceImpl implements ProfileService, SynchronousBundleList return; } + // First apply patches on existing items + + patchService.patch(bundleContext.getBundle().findEntries("META-INF/cxs/properties", "*-patch.json", true), PropertyType.class); + List<PropertyType> bundlePropertyTypes = new ArrayList<>(); while (predefinedPropertyTypeEntries.hasMoreElements()) { URL predefinedPropertyTypeURL = predefinedPropertyTypeEntries.nextElement(); - logger.debug("Found predefined property type at " + predefinedPropertyTypeURL + ", loading... "); + if (!predefinedPropertyTypeURL.getFile().endsWith("-patch.json")) { + logger.debug("Found predefined property type at " + predefinedPropertyTypeURL + ", loading... "); - try { - PropertyType propertyType = CustomObjectMapper.getObjectMapper().readValue(predefinedPropertyTypeURL, PropertyType.class); - // Register only if property type does not exist yet - if (getPropertyType(propertyType.getMetadata().getId()) == null || bundleContext.getBundle().getVersion().toString().contains("SNAPSHOT")) { + try { + PropertyType propertyType = CustomObjectMapper.getObjectMapper().readValue(predefinedPropertyTypeURL, PropertyType.class); + // Register only if property type does not exist yet + if (getPropertyType(propertyType.getMetadata().getId()) == null) { - setPropertyTypeTarget(predefinedPropertyTypeURL, propertyType); + setPropertyTypeTarget(predefinedPropertyTypeURL, propertyType); - persistenceService.save(propertyType); - bundlePropertyTypes.add(propertyType); - logger.info("Predefined property type with id {} registered", propertyType.getMetadata().getId()); - } else { - logger.info("The predefined property type with id {} is already registered, this property type will be skipped", propertyType.getMetadata().getId()); + persistenceService.save(propertyType); + bundlePropertyTypes.add(propertyType); + logger.info("Predefined property type with id {} registered", propertyType.getMetadata().getId()); + } else { + logger.info("The predefined property type with id {} is already registered, this property type will be skipped", propertyType.getMetadata().getId()); + } + } catch (IOException e) { + logger.error("Error while loading properties " + predefinedPropertyTypeURL, e); } - } catch (IOException e) { - logger.error("Error while loading properties " + predefinedPropertyTypeURL, e); } } propertyTypes = propertyTypes.with(bundlePropertyTypes); } + public void bundleChanged(BundleEvent event) { switch (event.getType()) { case BundleEvent.STARTED: http://git-wip-us.apache.org/repos/asf/incubator-unomi/blob/a65f28f8/services/src/main/java/org/apache/unomi/services/services/RulesServiceImpl.java ---------------------------------------------------------------------- diff --git a/services/src/main/java/org/apache/unomi/services/services/RulesServiceImpl.java b/services/src/main/java/org/apache/unomi/services/services/RulesServiceImpl.java index 2bcf564..3f63a31 100644 --- a/services/src/main/java/org/apache/unomi/services/services/RulesServiceImpl.java +++ b/services/src/main/java/org/apache/unomi/services/services/RulesServiceImpl.java @@ -17,20 +17,14 @@ package org.apache.unomi.services.services; -import org.apache.unomi.api.Event; -import org.apache.unomi.api.Item; -import org.apache.unomi.api.Metadata; -import org.apache.unomi.api.PartialList; +import org.apache.unomi.api.*; import org.apache.unomi.api.actions.Action; import org.apache.unomi.api.actions.ActionExecutor; import org.apache.unomi.api.conditions.Condition; import org.apache.unomi.api.query.Query; import org.apache.unomi.api.rules.Rule; import org.apache.unomi.api.rules.RuleStatistics; -import org.apache.unomi.api.services.DefinitionsService; -import org.apache.unomi.api.services.EventListenerService; -import org.apache.unomi.api.services.EventService; -import org.apache.unomi.api.services.RulesService; +import org.apache.unomi.api.services.*; import org.apache.unomi.persistence.spi.CustomObjectMapper; import org.apache.unomi.persistence.spi.PersistenceService; import org.apache.unomi.services.actions.ActionExecutorDispatcher; @@ -55,6 +49,8 @@ public class RulesServiceImpl implements RulesService, EventListenerService, Syn private EventService eventService; + private PatchService patchService; + private ActionExecutorDispatcher actionExecutorDispatcher; private List<Rule> allRules; @@ -83,6 +79,10 @@ public class RulesServiceImpl implements RulesService, EventListenerService, Syn this.actionExecutorDispatcher = actionExecutorDispatcher; } + public void setPatchService(PatchService patchService) { + this.patchService = patchService; + } + public void bindExecutor(ServiceReference<ActionExecutor> actionExecutorServiceReference) { ActionExecutor actionExecutor = bundleContext.getService(actionExecutorServiceReference); actionExecutorDispatcher.addExecutor(actionExecutorServiceReference.getProperty("actionExecutorId").toString(), actionExecutor); @@ -146,23 +146,27 @@ public class RulesServiceImpl implements RulesService, EventListenerService, Syn return; } + // First apply patches on existing items + patchService.patch(bundleContext.getBundle().findEntries("META-INF/cxs/rules", "*-patch.json", true), Rule.class); + while (predefinedRuleEntries.hasMoreElements()) { - URL predefinedSegmentURL = predefinedRuleEntries.nextElement(); - logger.debug("Found predefined rule at " + predefinedSegmentURL + ", loading... "); - - try { - Rule rule = CustomObjectMapper.getObjectMapper().readValue(predefinedSegmentURL, Rule.class); - // Register only if rule does not exist yet - if (getRule(rule.getMetadata().getId()) == null || bundleContext.getBundle().getVersion().toString().contains("SNAPSHOT")) { - setRule(rule); - logger.info("Predefined rule with id {} registered", rule.getMetadata().getId()); - } else { - logger.info("The predefined rule with id {} is already registered, this rule will be skipped", rule.getMetadata().getId()); + URL predefinedRuleURL = predefinedRuleEntries.nextElement(); + if (!predefinedRuleURL.getFile().endsWith("-patch.json")) { + logger.debug("Found predefined rule at " + predefinedRuleURL + ", loading... "); + + try { + Rule rule = CustomObjectMapper.getObjectMapper().readValue(predefinedRuleURL, Rule.class); + // Register only if rule does not exist yet + if (getRule(rule.getMetadata().getId()) == null) { + setRule(rule); + logger.info("Predefined rule with id {} registered", rule.getMetadata().getId()); + } else { + logger.info("The predefined rule with id {} is already registered, this rule will be skipped", rule.getMetadata().getId()); + } + } catch (IOException e) { + logger.error("Error while loading rule definition " + predefinedRuleURL, e); } - } catch (IOException e) { - logger.error("Error while loading segment definition " + predefinedSegmentURL, e); } - } } http://git-wip-us.apache.org/repos/asf/incubator-unomi/blob/a65f28f8/services/src/main/java/org/apache/unomi/services/services/SegmentServiceImpl.java ---------------------------------------------------------------------- diff --git a/services/src/main/java/org/apache/unomi/services/services/SegmentServiceImpl.java b/services/src/main/java/org/apache/unomi/services/services/SegmentServiceImpl.java index b1dfbb6..5b92a48 100644 --- a/services/src/main/java/org/apache/unomi/services/services/SegmentServiceImpl.java +++ b/services/src/main/java/org/apache/unomi/services/services/SegmentServiceImpl.java @@ -25,10 +25,7 @@ import org.apache.unomi.api.conditions.ConditionType; import org.apache.unomi.api.query.Query; import org.apache.unomi.api.rules.Rule; import org.apache.unomi.api.segments.*; -import org.apache.unomi.api.services.DefinitionsService; -import org.apache.unomi.api.services.EventService; -import org.apache.unomi.api.services.RulesService; -import org.apache.unomi.api.services.SegmentService; +import org.apache.unomi.api.services.*; import org.apache.unomi.persistence.spi.CustomObjectMapper; import org.apache.unomi.persistence.spi.PersistenceService; import org.apache.unomi.persistence.spi.aggregate.TermsAggregate; @@ -54,6 +51,8 @@ public class SegmentServiceImpl extends AbstractServiceImpl implements SegmentSe private RulesService rulesService; + private PatchService patchService; + private long taskExecutionPeriod = 24L * 60L * 60L * 1000L; private List<Segment> allSegments; private List<Scoring> allScoring; @@ -76,6 +75,10 @@ public class SegmentServiceImpl extends AbstractServiceImpl implements SegmentSe this.rulesService = rulesService; } + public void setPatchService(PatchService patchService) { + this.patchService = patchService; + } + public void setSegmentUpdateBatchSize(int segmentUpdateBatchSize) { this.segmentUpdateBatchSize = segmentUpdateBatchSize; } @@ -127,24 +130,30 @@ public class SegmentServiceImpl extends AbstractServiceImpl implements SegmentSe if (predefinedSegmentEntries == null) { return; } + + // First apply patches on existing items + patchService.patch(bundleContext.getBundle().findEntries("META-INF/cxs/segments", "*-patch.json", true), Segment.class); + while (predefinedSegmentEntries.hasMoreElements()) { URL predefinedSegmentURL = predefinedSegmentEntries.nextElement(); - logger.debug("Found predefined segment at " + predefinedSegmentURL + ", loading... "); + if (!predefinedSegmentURL.getFile().endsWith("-patch.json")) { + 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"); - } - // Register only if segment does not exist yet - if (getSegmentDefinition(segment.getMetadata().getId()) == null || bundleContext.getBundle().getVersion().toString().contains("SNAPSHOT")) { - setSegmentDefinition(segment); - logger.info("Predefined segment with id {} registered", segment.getMetadata().getId()); - } else { - logger.info("The predefined segment with id {} is already registered, this segment will be skipped", segment.getMetadata().getId()); + try { + Segment segment = CustomObjectMapper.getObjectMapper().readValue(predefinedSegmentURL, Segment.class); + if (segment.getMetadata().getScope() == null) { + segment.getMetadata().setScope("systemscope"); + } + // Register only if segment does not exist yet + if (getSegmentDefinition(segment.getMetadata().getId()) == null) { + setSegmentDefinition(segment); + logger.info("Predefined segment with id {} registered", segment.getMetadata().getId()); + } else { + logger.info("The predefined segment with id {} is already registered, this segment will be skipped", segment.getMetadata().getId()); + } + } catch (IOException e) { + logger.error("Error while loading segment definition " + predefinedSegmentURL, e); } - } catch (IOException e) { - logger.error("Error while loading segment definition " + predefinedSegmentURL, e); } } } @@ -154,24 +163,30 @@ public class SegmentServiceImpl extends AbstractServiceImpl implements SegmentSe if (predefinedScoringEntries == null) { return; } + + // First apply patches on existing items + patchService.patch(bundleContext.getBundle().findEntries("META-INF/cxs/scoring", "*-patch.json", true), Scoring.class); + while (predefinedScoringEntries.hasMoreElements()) { URL predefinedScoringURL = predefinedScoringEntries.nextElement(); - logger.debug("Found predefined scoring at " + predefinedScoringURL + ", loading... "); + if (!predefinedScoringURL.getFile().endsWith("-patch.json")) { + 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"); - } - // Register only if scoring plan does not exist yet - if (getScoringDefinition(scoring.getMetadata().getId()) == null || bundleContext.getBundle().getVersion().toString().contains("SNAPSHOT")) { - setScoringDefinition(scoring); - logger.info("Predefined scoring with id {} registered", scoring.getMetadata().getId()); - } else { - logger.info("The predefined scoring with id {} is already registered, this scoring will be skipped", scoring.getMetadata().getId()); + try { + Scoring scoring = CustomObjectMapper.getObjectMapper().readValue(predefinedScoringURL, Scoring.class); + if (scoring.getMetadata().getScope() == null) { + scoring.getMetadata().setScope("systemscope"); + } + // Register only if scoring plan does not exist yet + if (getScoringDefinition(scoring.getMetadata().getId()) == null) { + setScoringDefinition(scoring); + logger.info("Predefined scoring with id {} registered", scoring.getMetadata().getId()); + } else { + logger.info("The predefined scoring with id {} is already registered, this scoring will be skipped", scoring.getMetadata().getId()); + } + } catch (IOException e) { + logger.error("Error while loading segment definition " + predefinedScoringURL, e); } - } catch (IOException e) { - logger.error("Error while loading segment definition " + predefinedScoringURL, e); } } } http://git-wip-us.apache.org/repos/asf/incubator-unomi/blob/a65f28f8/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 2e1ee27..438c7d0 100644 --- a/services/src/main/resources/OSGI-INF/blueprint/blueprint.xml +++ b/services/src/main/resources/OSGI-INF/blueprint/blueprint.xml @@ -58,6 +58,7 @@ <bean id="definitionsServiceImpl" class="org.apache.unomi.services.services.DefinitionsServiceImpl" init-method="postConstruct" destroy-method="preDestroy"> <property name="persistenceService" ref="persistenceService"/> + <property name="patchService" ref="patchServiceImpl"/> <property name="bundleContext" ref="blueprintBundleContext"/> </bean> <service id="definitionsService" ref="definitionsServiceImpl"> @@ -100,6 +101,7 @@ init-method="postConstruct" destroy-method="preDestroy"> <property name="persistenceService" ref="persistenceService"/> <property name="definitionsService" ref="definitionsServiceImpl"/> + <property name="patchService" ref="patchServiceImpl"/> <property name="rulesService" ref="rulesServiceImpl"/> <property name="bundleContext" ref="blueprintBundleContext"/> </bean> @@ -120,6 +122,7 @@ <property name="persistenceService" ref="persistenceService"/> <property name="definitionsService" ref="definitionsServiceImpl"/> <property name="eventService" ref="eventServiceImpl"/> + <property name="patchService" ref="patchServiceImpl"/> <property name="actionExecutorDispatcher" ref="actionExecutorDispatcherImpl"/> <property name="bundleContext" ref="blueprintBundleContext"/> </bean> @@ -137,6 +140,7 @@ <property name="definitionsService" ref="definitionsServiceImpl"/> <property name="eventService" ref="eventServiceImpl"/> <property name="rulesService" ref="rulesServiceImpl"/> + <property name="patchService" ref="patchServiceImpl"/> <property name="bundleContext" ref="blueprintBundleContext"/> <property name="taskExecutionPeriod" value="86400000"/> <property name="segmentUpdateBatchSize" value="${services.segment.update.batchSize}" /> @@ -167,6 +171,7 @@ <property name="definitionsService" ref="definitionsServiceImpl"/> <property name="segmentService" ref="segmentServiceImpl"/> <property name="queryService" ref="queryServiceImpl"/> + <property name="patchService" ref="patchServiceImpl"/> <property name="bundleContext" ref="blueprintBundleContext"/> <property name="purgeProfileInterval" value="${services.profile.purge.interval}"/> <property name="purgeProfileInactiveTime" value="${services.profile.purge.inactiveTime}"/> @@ -209,6 +214,17 @@ </bean> <service id="personalizationService" ref="personalizationServiceImpl" interface="org.apache.unomi.api.services.PersonalizationService" /> + <bean id="patchServiceImpl" class="org.apache.unomi.services.services.PatchServiceImpl"> + <property name="persistenceService" ref="persistenceService"/> + <property name="bundleContext" ref="blueprintBundleContext"/> + </bean> + <service id="patchService" ref="patchServiceImpl"> + <interfaces> + <value>org.apache.unomi.api.services.PatchService</value> + </interfaces> + </service> + + <!-- 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.apache.unomi.api.services.EventListenerService" http://git-wip-us.apache.org/repos/asf/incubator-unomi/blob/a65f28f8/tools/shell-dev-commands/src/main/java/org/apache/unomi/shell/commands/DeployDefinition.java ---------------------------------------------------------------------- diff --git a/tools/shell-dev-commands/src/main/java/org/apache/unomi/shell/commands/DeployDefinition.java b/tools/shell-dev-commands/src/main/java/org/apache/unomi/shell/commands/DeployDefinition.java deleted file mode 100644 index e4cbd7f..0000000 --- a/tools/shell-dev-commands/src/main/java/org/apache/unomi/shell/commands/DeployDefinition.java +++ /dev/null @@ -1,221 +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.apache.unomi.shell.commands; - -import org.apache.commons.lang3.StringUtils; -import org.apache.felix.service.command.CommandSession; -import org.apache.karaf.shell.commands.Argument; -import org.apache.karaf.shell.commands.Command; -import org.apache.karaf.shell.console.OsgiCommandSupport; -import org.apache.unomi.api.Persona; -import org.apache.unomi.api.PersonaWithSessions; -import org.apache.unomi.api.PropertyType; -import org.apache.unomi.api.actions.ActionType; -import org.apache.unomi.api.campaigns.Campaign; -import org.apache.unomi.api.conditions.ConditionType; -import org.apache.unomi.api.goals.Goal; -import org.apache.unomi.api.rules.Rule; -import org.apache.unomi.api.segments.Scoring; -import org.apache.unomi.api.segments.Segment; -import org.apache.unomi.api.services.*; -import org.apache.unomi.persistence.spi.CustomObjectMapper; -import org.jline.reader.LineReader; -import org.osgi.framework.Bundle; - -import java.io.IOException; -import java.net.URL; -import java.util.Arrays; -import java.util.Enumeration; -import java.util.List; - -@Command(scope = "unomi", name = "deploy-definition", description = "This will deploy a specific definition") -public class DeployDefinition extends OsgiCommandSupport { - - private DefinitionsService definitionsService; - private GoalsService goalsService; - private ProfileService profileService; - private RulesService rulesService; - private SegmentService segmentService; - - private List<String> definitionTypes = Arrays.asList("condition", "action", "goal", "campaign", "persona", "persona with sessions", "property", "rule", "segment", "scoring"); - - @Argument(index = 0, name = "bundleId", description = "The bundle identifier where to find the definition", required = true, multiValued = false) - Long bundleIdentifier; - - @Argument(index = 1, name = "fileName", description = "The name of the file which contains the definition, without its extension (e.g: firstName)", required = true, multiValued = false) - String fileName; - - protected Object doExecute() throws Exception { - Bundle bundleToUpdate = bundleContext.getBundle(bundleIdentifier); - if (bundleToUpdate == null) { - System.out.println("Couldn't find a bundle with id: " + bundleIdentifier); - return null; - } - - String definitionTypeAnswer = askUserWithAuthorizedAnswer(session,"Which kind of definition do you want to load?" + getDefinitionTypesWithNumber() + "\n", Arrays.asList("0", "1", "2", "3", "4", "5", "6", "7", "8", "9")); - String definitionType = definitionTypes.get(new Integer(definitionTypeAnswer)); - - String path = getDefinitionTypePath(definitionType); - Enumeration<URL> definitions = bundleToUpdate.findEntries(path, "*.json", true); - if (definitions == null) { - System.out.println("Couldn't find definitions in bundle with id: " + bundleIdentifier + " and definition path: " + path); - return null; - } - - while (definitions.hasMoreElements()) { - URL definitionURL = definitions.nextElement(); - if (definitionURL.toString().contains(fileName)) { - System.out.println("Found definition at " + definitionURL + ", loading... "); - - updateDefinition(definitionType, definitionURL); - } - } - - return null; - } - - private String askUserWithAuthorizedAnswer(CommandSession session, String msg, List<String> authorizedAnswer) throws IOException { - String answer; - do { - answer = promptMessageToUser(session,msg); - } while (!authorizedAnswer.contains(answer.toLowerCase())); - return answer; - } - - private String promptMessageToUser(CommandSession session, String msg) throws IOException { - LineReader reader = (LineReader) session.get(".jline.reader"); - return reader.readLine(msg, null); - } - - private String getDefinitionTypesWithNumber() { - StringBuilder definitionTypesWithNumber = new StringBuilder(); - for (int i = 0; i < definitionTypes.size(); i++) { - definitionTypesWithNumber.append("\n").append(i).append(". ").append(definitionTypes.get(i)); - } - return definitionTypesWithNumber.toString(); - } - - private void updateDefinition(String definitionType, URL definitionURL) { - try { - switch (definitionType) { - case "condition": - ConditionType conditionType = CustomObjectMapper.getObjectMapper().readValue(definitionURL, ConditionType.class); - definitionsService.setConditionType(conditionType); - break; - case "action": - ActionType actionType = CustomObjectMapper.getObjectMapper().readValue(definitionURL, ActionType.class); - definitionsService.setActionType(actionType); - break; - case "goal": - Goal goal = CustomObjectMapper.getObjectMapper().readValue(definitionURL, Goal.class); - goalsService.setGoal(goal); - break; - case "campaign": - Campaign campaign = CustomObjectMapper.getObjectMapper().readValue(definitionURL, Campaign.class); - goalsService.setCampaign(campaign); - break; - case "persona": - Persona persona = CustomObjectMapper.getObjectMapper().readValue(definitionURL, Persona.class); - profileService.savePersona(persona); - break; - case "persona with session": - PersonaWithSessions personaWithSessions = CustomObjectMapper.getObjectMapper().readValue(definitionURL, PersonaWithSessions.class); - profileService.savePersonaWithSessions(personaWithSessions); - break; - case "property": - PropertyType propertyType = CustomObjectMapper.getObjectMapper().readValue(definitionURL, PropertyType.class); - profileService.setPropertyTypeTarget(definitionURL, propertyType); - profileService.setPropertyType(propertyType); - break; - case "rule": - Rule rule = CustomObjectMapper.getObjectMapper().readValue(definitionURL, Rule.class); - rulesService.setRule(rule); - break; - case "segment": - Segment segment = CustomObjectMapper.getObjectMapper().readValue(definitionURL, Segment.class); - segmentService.setSegmentDefinition(segment); - break; - case "scoring": - Scoring scoring = CustomObjectMapper.getObjectMapper().readValue(definitionURL, Scoring.class); - segmentService.setScoringDefinition(scoring); - break; - } - System.out.println("Predefined definition registered"); - } catch (IOException e) { - System.out.println("Error while saving definition " + definitionURL); - System.out.println(e.getMessage()); - } - } - - private String getDefinitionTypePath(String definitionType) { - StringBuilder path = new StringBuilder("META-INF/cxs/"); - switch (definitionType) { - case "condition": - path.append("conditions"); - break; - case "action": - path.append("actions"); - break; - case "goal": - path.append("goals"); - break; - case "campaign": - path.append("campaigns"); - break; - case "persona": - path.append("personas"); - break; - case "persona with session": - path.append("personas"); - break; - case "property": - path.append("properties"); - break; - case "rule": - path.append("rules"); - break; - case "segment": - path.append("segments"); - break; - case "scoring": - path.append("scoring"); - break; - } - - return path.toString(); - } - - public void setDefinitionsService(DefinitionsService definitionsService) { - this.definitionsService = definitionsService; - } - - public void setGoalsService(GoalsService goalsService) { - this.goalsService = goalsService; - } - - public void setProfileService(ProfileService profileService) { - this.profileService = profileService; - } - - public void setRulesService(RulesService rulesService) { - this.rulesService = rulesService; - } - - public void setSegmentService(SegmentService segmentService) { - this.segmentService = segmentService; - } -} http://git-wip-us.apache.org/repos/asf/incubator-unomi/blob/a65f28f8/tools/shell-dev-commands/src/main/java/org/apache/unomi/shell/commands/DeployDefinitionCommand.java ---------------------------------------------------------------------- diff --git a/tools/shell-dev-commands/src/main/java/org/apache/unomi/shell/commands/DeployDefinitionCommand.java b/tools/shell-dev-commands/src/main/java/org/apache/unomi/shell/commands/DeployDefinitionCommand.java new file mode 100644 index 0000000..587ec7f --- /dev/null +++ b/tools/shell-dev-commands/src/main/java/org/apache/unomi/shell/commands/DeployDefinitionCommand.java @@ -0,0 +1,297 @@ +/* + * 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.shell.commands; + +import org.apache.commons.lang3.StringUtils; +import org.apache.felix.service.command.CommandSession; +import org.apache.karaf.shell.commands.Argument; +import org.apache.karaf.shell.commands.Command; +import org.apache.karaf.shell.console.OsgiCommandSupport; +import org.apache.unomi.api.*; +import org.apache.unomi.api.actions.ActionType; +import org.apache.unomi.api.campaigns.Campaign; +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.rules.Rule; +import org.apache.unomi.api.segments.Scoring; +import org.apache.unomi.api.segments.Segment; +import org.apache.unomi.api.services.*; +import org.apache.unomi.persistence.spi.CustomObjectMapper; +import org.jline.reader.LineReader; +import org.osgi.framework.Bundle; + +import java.io.IOException; +import java.net.URL; +import java.util.*; +import java.util.stream.Collectors; +import java.util.stream.IntStream; + +@Command(scope = "unomi", name = "deploy-definition", description = "This will deploy a specific definition") +public class DeployDefinitionCommand extends OsgiCommandSupport { + + private DefinitionsService definitionsService; + private GoalsService goalsService; + private ProfileService profileService; + private RulesService rulesService; + private SegmentService segmentService; + private PatchService patchService; + + private final static List<String> definitionTypes = Arrays.asList("condition", "action", "goal", "campaign", "persona", "property", "rule", "segment", "scoring"); + + + @Argument(index = 0, name = "bundleId", description = "The bundle identifier where to find the definition", multiValued = false) + Long bundleIdentifier; + + @Argument(index = 1, name = "type", description = "The kind of definitions you want to load (e.g.: condition, action, ..)", required = false, multiValued = false) + String definitionType; + + @Argument(index = 2, name = "fileName", description = "The name of the file which contains the definition, without its extension (e.g: firstName)", required = false, multiValued = false) + String fileName; + + protected Object doExecute() throws Exception { + Bundle bundleToUpdate; + if (bundleIdentifier == null) { + List<Bundle> bundles = new ArrayList<>(); + for (Bundle bundle : bundleContext.getBundles()) { + if (bundle.findEntries("META-INF/cxs/", "*.json", true) != null) { + bundles.add(bundle); + } + } + + String bundleAnswer = askUserWithAuthorizedAnswer(session, "Which bundle ?" + getValuesWithNumber(bundles.stream().map(Bundle::getSymbolicName).collect(Collectors.toList())) + "\n", + IntStream.range(1,bundles.size()+1).mapToObj(Integer::toString).collect(Collectors.toList())); + bundleToUpdate = bundles.get(new Integer(bundleAnswer)-1); + bundleIdentifier = bundleToUpdate.getBundleId(); + } else { + bundleToUpdate = bundleContext.getBundle(bundleIdentifier); + } + + if (bundleToUpdate == null) { + System.out.println("Couldn't find a bundle with id: " + bundleIdentifier); + return null; + } + + + if (definitionType == null) { + List<String> values = definitionTypes.stream().filter((t) -> bundleToUpdate.findEntries(getDefinitionTypePath(t), "*.json", true) != null).collect(Collectors.toList()); + String definitionTypeAnswer = askUserWithAuthorizedAnswer(session, "Which kind of definition do you want to load?" + getValuesWithNumber(values) + "\n", + IntStream.range(1,values.size()+1).mapToObj(Integer::toString).collect(Collectors.toList())); + definitionType = values.get(new Integer(definitionTypeAnswer)-1); + } + + if (!definitionTypes.contains(definitionType)) { + System.out.println("Invalid type '" + definitionType + "' , allowed values : " +definitionTypes); + return null; + } + + String path = getDefinitionTypePath(definitionType); + Enumeration<URL> definitions = bundleToUpdate.findEntries(path, "*.json", true); + if (definitions == null) { + System.out.println("Couldn't find definitions in bundle with id: " + bundleIdentifier + " and definition path: " + path); + return null; + } + + List<URL> values = new ArrayList<>(); + while (definitions.hasMoreElements()) { + values.add(definitions.nextElement()); + } + if (fileName == null) { + List<String> stringList = values.stream().map(u -> StringUtils.substringAfterLast(u.getFile(), "/")).collect(Collectors.toList()); + Collections.sort(stringList); + stringList.add(0, "* (All)"); + String fileNameAnswer = askUserWithAuthorizedAnswer(session, "Which file do you want to load ?" + getValuesWithNumber(stringList) + "\n", + IntStream.range(1,stringList.size()+1).mapToObj(Integer::toString).collect(Collectors.toList())); + fileName = stringList.get(new Integer(fileNameAnswer)-1); + } + if (fileName.startsWith("*")) { + for (URL url : values) { + if (!url.getFile().endsWith("-patch.json")) { + updateDefinition(definitionType, url); + } + } + } else { + if (!fileName.contains("/")) { + fileName = "/" + fileName; + } + if (!fileName.endsWith(".json")) { + fileName += ".json"; + } + + Optional<URL> optionalURL = values.stream().filter(u -> u.getFile().endsWith(fileName)).findFirst(); + if (optionalURL.isPresent()) { + URL url = optionalURL.get(); + if (!url.getFile().endsWith("-patch.json")) { + updateDefinition(definitionType, url); + } else { + deployPatch(definitionType, url); + } + } else { + System.out.println("Couldn't find file " + fileName); + return null; + } + } + + return null; + } + + private String askUserWithAuthorizedAnswer(CommandSession session, String msg, List<String> authorizedAnswer) throws IOException { + String answer; + do { + answer = promptMessageToUser(session,msg); + } while (!authorizedAnswer.contains(answer.toLowerCase())); + return answer; + } + + private String promptMessageToUser(CommandSession session, String msg) throws IOException { + LineReader reader = (LineReader) session.get(".jline.reader"); + return reader.readLine(msg, null); + } + + private String getValuesWithNumber(List<String> values) { + StringBuilder definitionTypesWithNumber = new StringBuilder(); + for (int i = 0; i < values.size(); i++) { + definitionTypesWithNumber.append("\n").append(i+1).append(". ").append(values.get(i)); + } + return definitionTypesWithNumber.toString(); + } + + private void updateDefinition(String definitionType, URL definitionURL) { + try { + + switch (definitionType) { + case "condition": + ConditionType conditionType = CustomObjectMapper.getObjectMapper().readValue(definitionURL, ConditionType.class); + definitionsService.setConditionType(conditionType); + break; + case "action": + ActionType actionType = CustomObjectMapper.getObjectMapper().readValue(definitionURL, ActionType.class); + definitionsService.setActionType(actionType); + break; + case "goal": + Goal goal = CustomObjectMapper.getObjectMapper().readValue(definitionURL, Goal.class); + goalsService.setGoal(goal); + break; + case "campaign": + Campaign campaign = CustomObjectMapper.getObjectMapper().readValue(definitionURL, Campaign.class); + goalsService.setCampaign(campaign); + break; + case "persona": + PersonaWithSessions persona = CustomObjectMapper.getObjectMapper().readValue(definitionURL, PersonaWithSessions.class); + profileService.savePersonaWithSessions(persona); + break; + case "property": + PropertyType propertyType = CustomObjectMapper.getObjectMapper().readValue(definitionURL, PropertyType.class); + profileService.setPropertyTypeTarget(definitionURL, propertyType); + profileService.setPropertyType(propertyType); + break; + case "rule": + Rule rule = CustomObjectMapper.getObjectMapper().readValue(definitionURL, Rule.class); + rulesService.setRule(rule); + break; + case "segment": + Segment segment = CustomObjectMapper.getObjectMapper().readValue(definitionURL, Segment.class); + segmentService.setSegmentDefinition(segment); + break; + case "scoring": + Scoring scoring = CustomObjectMapper.getObjectMapper().readValue(definitionURL, Scoring.class); + segmentService.setScoringDefinition(scoring); + break; + } + System.out.println("Predefined definition registered : "+definitionURL.getFile()); + } catch (IOException e) { + System.out.println("Error while saving definition " + definitionURL); + System.out.println(e.getMessage()); + } + } + + private void deployPatch(String definitionType, URL patchURL) { + try { + Patch patch = CustomObjectMapper.getObjectMapper().readValue(patchURL, Patch.class); + Class<? extends Item> type = Patch.PATCHABLE_TYPES.get(definitionType); + if (type != null) { + patchService.patch(patch, type); + } + + System.out.println("Definition patched : "+ patch.getItemId() + " by : " + patchURL.getFile()); + } catch (IOException e) { + System.out.println("Error while saving definition " + patchURL); + System.out.println(e.getMessage()); + } + } + + + + private String getDefinitionTypePath(String definitionType) { + StringBuilder path = new StringBuilder("META-INF/cxs/"); + switch (definitionType) { + case "condition": + path.append("conditions"); + break; + case "action": + path.append("actions"); + break; + case "goal": + path.append("goals"); + break; + case "campaign": + path.append("campaigns"); + break; + case "persona": + path.append("personas"); + break; + case "property": + path.append("properties"); + break; + case "rule": + path.append("rules"); + break; + case "segment": + path.append("segments"); + break; + case "scoring": + path.append("scoring"); + break; + } + + return path.toString(); + } + + public void setDefinitionsService(DefinitionsService definitionsService) { + this.definitionsService = definitionsService; + } + + public void setGoalsService(GoalsService goalsService) { + this.goalsService = goalsService; + } + + public void setProfileService(ProfileService profileService) { + this.profileService = profileService; + } + + public void setRulesService(RulesService rulesService) { + this.rulesService = rulesService; + } + + public void setSegmentService(SegmentService segmentService) { + this.segmentService = segmentService; + } + + public void setPatchService(PatchService patchService) { + this.patchService = patchService; + } +} http://git-wip-us.apache.org/repos/asf/incubator-unomi/blob/a65f28f8/tools/shell-dev-commands/src/main/resources/OSGI-INF/blueprint/blueprint.xml ---------------------------------------------------------------------- diff --git a/tools/shell-dev-commands/src/main/resources/OSGI-INF/blueprint/blueprint.xml b/tools/shell-dev-commands/src/main/resources/OSGI-INF/blueprint/blueprint.xml index 8fb4530..8144a21 100644 --- a/tools/shell-dev-commands/src/main/resources/OSGI-INF/blueprint/blueprint.xml +++ b/tools/shell-dev-commands/src/main/resources/OSGI-INF/blueprint/blueprint.xml @@ -30,6 +30,7 @@ <reference id="clusterService" interface="org.apache.unomi.api.services.ClusterService"/> <reference id="queryService" interface="org.apache.unomi.api.services.QueryService"/> <reference id="eventService" interface="org.apache.unomi.api.services.EventService"/> + <reference id="patchService" interface="org.apache.unomi.api.services.PatchService"/> <shell:command-bundle> <shell:command> @@ -68,12 +69,13 @@ </shell:action> </shell:command> <shell:command> - <shell:action class="org.apache.unomi.shell.commands.DeployDefinition"> + <shell:action class="org.apache.unomi.shell.commands.DeployDefinitionCommand"> <shell:property name="definitionsService" ref="definitionsService" /> <shell:property name="goalsService" ref="goalsService" /> <shell:property name="profileService" ref="profileService" /> <shell:property name="rulesService" ref="rulesService" /> <shell:property name="segmentService" ref="segmentService" /> + <shell:property name="patchService" ref="patchService" /> </shell:action> </shell:command> <shell:command>