blob: bb70aa08e4d3bc2535cbcf30722b9a72bd03b8c3 [file] [log] [blame]
/*
* 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.graphql.commands.segments;
import com.google.common.base.Strings;
import graphql.schema.DataFetchingEnvironment;
import graphql.schema.GraphQLInputObjectType;
import org.apache.unomi.api.Metadata;
import org.apache.unomi.api.conditions.Condition;
import org.apache.unomi.api.conditions.ConditionType;
import org.apache.unomi.api.segments.Segment;
import org.apache.unomi.api.services.DefinitionsService;
import org.apache.unomi.api.services.SegmentService;
import org.apache.unomi.graphql.commands.BaseCommand;
import org.apache.unomi.graphql.schema.ComparisonConditionTranslator;
import org.apache.unomi.graphql.schema.PropertyNameTranslator;
import org.apache.unomi.graphql.schema.PropertyValueTypeHelper;
import org.apache.unomi.graphql.types.input.CDPConsentUpdateEventFilterInput;
import org.apache.unomi.graphql.types.input.CDPEventFilterInput;
import org.apache.unomi.graphql.types.input.CDPInterestFilterInput;
import org.apache.unomi.graphql.types.input.CDPProfileEventsFilterInput;
import org.apache.unomi.graphql.types.input.CDPProfileFilterInput;
import org.apache.unomi.graphql.types.input.CDPProfilePropertiesFilterInput;
import org.apache.unomi.graphql.types.input.CDPSegmentInput;
import org.apache.unomi.graphql.types.input.CDPSessionEventFilterInput;
import org.apache.unomi.graphql.types.output.CDPSegment;
import org.apache.unomi.graphql.utils.ConditionBuilder;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.Collections;
import java.util.List;
import java.util.Map;
import java.util.function.Function;
import java.util.stream.Collectors;
import static org.apache.unomi.graphql.CDPGraphQLConstants.SEGMENT_ARGUMENT_NAME;
public class CreateOrUpdateSegmentCommand extends BaseCommand<CDPSegment> {
private final CDPSegmentInput segmentInput;
private final DataFetchingEnvironment environment;
private final ConditionType profileSegmentConditionType;
private final ConditionType profilePropertyConditionType;
private final ConditionType booleanConditionType;
private final ConditionType profileUserListConditionType;
private final ConditionType eventPropertyConditionType;
private final ConditionType pastEventConditionType;
private final ConditionType notConditionType;
private CreateOrUpdateSegmentCommand(Builder builder) {
super(builder);
this.segmentInput = builder.segmentInput;
this.environment = builder.environment;
final DefinitionsService definitionsService = serviceManager.getDefinitionsService();
this.profileSegmentConditionType = definitionsService.getConditionType("profileSegmentCondition");
this.profilePropertyConditionType = definitionsService.getConditionType("profilePropertyCondition");
this.booleanConditionType = definitionsService.getConditionType("booleanCondition");
this.profileUserListConditionType = definitionsService.getConditionType("profileUserListCondition");
this.eventPropertyConditionType = definitionsService.getConditionType("eventPropertyCondition");
this.pastEventConditionType = definitionsService.getConditionType("pastEventCondition");
this.notConditionType = definitionsService.getConditionType("notCondition");
}
@Override
public CDPSegment execute() {
final SegmentService segmentService = serviceManager.getSegmentService();
final String segmentId = Strings.isNullOrEmpty(segmentInput.getId())
? segmentInput.getName()
: segmentInput.getId();
Segment segment = segmentService.getSegmentDefinition(segmentId);
if (segment == null) {
segment = new Segment();
segment.setItemType(Segment.ITEM_TYPE);
segment.setMetadata(createMedata(segmentId));
} else {
if (segment.getMetadata() == null) {
segment.setMetadata(new Metadata());
}
segment.setItemId(segmentId);
segment.getMetadata().setId(segmentId);
segment.getMetadata().setName(segmentInput.getName());
segment.getMetadata().setScope(segmentInput.getView());
}
final Condition condition = createSegmentCondition();
segment.setCondition(condition);
segmentService.setSegmentDefinition(segment);
return new CDPSegment(segment);
}
private Metadata createMedata(final String segmentId) {
final Metadata metadata = new Metadata();
metadata.setId(segmentId);
metadata.setName(segmentInput.getName());
metadata.setScope(segmentInput.getView());
return metadata;
}
private List<Condition> createSubCondition() {
final CDPProfileFilterInput filterInput = segmentInput.getProfiles();
if (filterInput == null) {
return Collections.emptyList();
}
final List<Condition> conditions = new ArrayList<>();
final Condition segmentsContainsCondition = createSegmentsContainsCondition(filterInput.getSegments_contains());
if (segmentsContainsCondition != null) {
conditions.add(segmentsContainsCondition);
}
final Condition profileIDsContainsCondition = createProfileIDsContainsCondition(filterInput.getProfileIDs_contains());
if (profileIDsContainsCondition != null) {
conditions.add(profileIDsContainsCondition);
}
final Condition listsContainsCondition = createListsContains(filterInput.getLists_contains());
if (listsContainsCondition != null) {
conditions.add(listsContainsCondition);
}
final Condition consentsContainsCondition = createConsentsContains(filterInput.getConsents_contains());
if (consentsContainsCondition != null) {
conditions.add(consentsContainsCondition);
}
final Condition profilePropertiesCondition = createProfilePropertiesCondition();
if (profilePropertiesCondition != null) {
conditions.add(profilePropertiesCondition);
}
final Condition profileInterestCondition = createProfileInterestCondition(filterInput.getInterests());
if (profileInterestCondition != null) {
conditions.add(profileInterestCondition);
}
final Condition profileEventsCondition = createProfileEventsCondition(filterInput.getEvents());
if (profileEventsCondition != null) {
conditions.add(profileEventsCondition);
}
return conditions;
}
private Condition createSegmentsContainsCondition(final List<String> segmentsContains) {
if (segmentsContains == null || segmentsContains.isEmpty()) {
return null;
}
return ConditionBuilder.builder(profileSegmentConditionType)
.setParameter("segments", segmentsContains)
.setParameter("matchType", "in").build();
}
private Condition createProfileIDsContainsCondition(final List<String> profileIDsContains) {
if (profileIDsContains == null || profileIDsContains.isEmpty()) {
return null;
}
final List<Condition> subConditions = profileIDsContains.stream()
.map(profileID -> ConditionBuilder.builder(profilePropertyConditionType)
.setPropertyName("itemId")
.setComparisonOperator("contains")
.setPropertyValue(profileID)
.build()
).collect(Collectors.toList());
return ConditionBuilder.builder(booleanConditionType).buildBooleanCondition("or", subConditions);
}
private Condition createListsContains(final List<String> listsContains) {
if (listsContains == null || listsContains.isEmpty()) {
return null;
}
return ConditionBuilder.builder(profileUserListConditionType)
.setParameter("lists", listsContains)
.setParameter("matchType", "in").build();
}
private Condition createConsentsContains(final List<String> consentsContains) {
if (consentsContains == null || consentsContains.isEmpty()) {
return null;
}
final List<Condition> subConditions = new ArrayList<>();
for (final String value : consentsContains) {
final String[] splittedValue = value.split("/", -1);
final Condition scopeCondition = ConditionBuilder.builder(profilePropertyConditionType)
.setPropertyName("consents." + value + ".scope")
.setComparisonOperator("equals")
.setPropertyValue(splittedValue[0]).build();
final Condition typeIdentifierCondition = ConditionBuilder.builder(profilePropertyConditionType)
.setPropertyName("consents." + value + ".typeIdentifier")
.setComparisonOperator("equals")
.setPropertyValue(splittedValue[1]).build();
final Condition statusCondition = ConditionBuilder.builder(profilePropertyConditionType)
.setPropertyName("consents." + value + ".status")
.setComparisonOperator("equals")
.setPropertyValue("GRANTED").build();
subConditions.add(ConditionBuilder.builder(booleanConditionType)
.buildBooleanCondition("and", Arrays.asList(scopeCondition, typeIdentifierCondition, statusCondition)));
}
return ConditionBuilder.builder(booleanConditionType).buildBooleanCondition("or", subConditions);
}
@SuppressWarnings("unchecked")
private Condition createProfilePropertiesCondition() {
final Map<String, Object> segmentArgumentAsMap = environment.getArgument(SEGMENT_ARGUMENT_NAME);
if (!segmentArgumentAsMap.containsKey("profiles")) {
return null;
}
final Map<String, Object> profilesAsMap = (Map<String, Object>) segmentArgumentAsMap.get("profiles");
if (!profilesAsMap.containsKey("properties")) {
return null;
}
final Map<String, Object> profileFilterPropertiesAsMap = (Map<String, Object>) profilesAsMap.get("properties");
return createProfilePropertiesCondition(profileFilterPropertiesAsMap);
}
@SuppressWarnings("unchecked")
private Condition createProfilePropertiesCondition(final Map<String, Object> profilePropertiesFilterInput) {
if (profilePropertiesFilterInput == null || profilePropertiesFilterInput.isEmpty()) {
return null;
}
final List<Condition> subConditions = new ArrayList<>();
profilePropertiesFilterInput.forEach((propertyName, value) -> {
if ("and".equals(propertyName) || "or".equals(propertyName)
&& profilePropertiesFilterInput.get(propertyName) != null) {
final List<Map<String, Object>> inputFilters = (List<Map<String, Object>>) profilePropertiesFilterInput.get(propertyName);
createConditionBasedOnFilterWithSubFilters(
subConditions, inputFilters, this::createProfilePropertiesCondition, propertyName);
} else {
final String[] propertyFilter = propertyName.split("_", -1);
final String propertyValueType =
PropertyValueTypeHelper.getPropertyValueParameterForInputType(
CDPProfilePropertiesFilterInput.TYPE_NAME, propertyName, environment);
subConditions.add(ConditionBuilder.builder(profilePropertyConditionType)
.setPropertyName("properties." + PropertyNameTranslator.translateFromGraphQLToUnomi(propertyFilter[0]))
.setComparisonOperator(ComparisonConditionTranslator.translateFromGraphQLToUnomi(propertyFilter[1]))
.setParameter(propertyValueType, value).build());
}
});
return ConditionBuilder.builder(booleanConditionType).buildBooleanCondition("and", subConditions);
}
private Condition createProfileInterestCondition(final CDPInterestFilterInput interestFilterInput) {
if (interestFilterInput == null) {
return null;
}
final String propertyName = "properties.interests." + interestFilterInput.getTopic_equals();
final List<Condition> subConditions = new ArrayList<>();
if (interestFilterInput.getScore_equals() != null) {
subConditions.add(ConditionBuilder.builder(profilePropertyConditionType)
.setPropertyName(propertyName)
.setComparisonOperator("equals")
.setPropertyValueInteger(interestFilterInput.getScore_equals()).build());
}
if (interestFilterInput.getScore_gte() != null) {
subConditions.add(ConditionBuilder.builder(profilePropertyConditionType)
.setPropertyName(propertyName)
.setComparisonOperator("greaterThanOrEqualTo")
.setPropertyValueInteger(interestFilterInput.getScore_gte()).build());
}
if (interestFilterInput.getScore_gt() != null) {
subConditions.add(ConditionBuilder.builder(profilePropertyConditionType)
.setPropertyName(propertyName)
.setComparisonOperator("greaterThan")
.setPropertyValueInteger(interestFilterInput.getScore_gt()).build());
}
if (interestFilterInput.getScore_lte() != null) {
subConditions.add(ConditionBuilder.builder(profilePropertyConditionType)
.setPropertyName(propertyName)
.setComparisonOperator("lessThanOrEqualTo")
.setPropertyValueInteger(interestFilterInput.getScore_lte()).build());
}
if (interestFilterInput.getScore_lt() != null) {
subConditions.add(ConditionBuilder.builder(profilePropertyConditionType)
.setPropertyName(propertyName)
.setComparisonOperator("lessThan")
.setPropertyValueInteger(interestFilterInput.getScore_lt()).build());
}
createConditionBasedOnFilterWithSubFilters(
subConditions, interestFilterInput.getAnd(), this::createProfileInterestCondition, "and");
createConditionBasedOnFilterWithSubFilters(
subConditions, interestFilterInput.getOr(), this::createProfileInterestCondition, "or");
return ConditionBuilder.builder(booleanConditionType).buildBooleanCondition("and", subConditions);
}
private Condition createEventPropertyCondition(final CDPEventFilterInput eventFilterInput) {
final List<Condition> subConditions = new ArrayList<>();
if (!Strings.isNullOrEmpty(eventFilterInput.getCdp_sourceID_equals())) {
subConditions.add(ConditionBuilder.builder(eventPropertyConditionType)
.setPropertyName("source.itemId")
.setComparisonOperator("equals")
.setPropertyValue(eventFilterInput.getCdp_sourceID_equals()).build());
}
if (!Strings.isNullOrEmpty(eventFilterInput.getCdp_profileID_equals())) {
subConditions.add(ConditionBuilder.builder(eventPropertyConditionType)
.setPropertyName("profileId")
.setComparisonOperator("equals")
.setPropertyValue(eventFilterInput.getCdp_profileID_equals()).build());
}
if (!Strings.isNullOrEmpty(eventFilterInput.getId_equals())) {
subConditions.add(ConditionBuilder.builder(eventPropertyConditionType)
.setPropertyName("itemId")
.setComparisonOperator("equals")
.setPropertyValue(eventFilterInput.getId_equals())
.build());
}
if (eventFilterInput.getCdp_timestamp_equals() != null) {
subConditions.add(ConditionBuilder.builder(eventPropertyConditionType)
.setPropertyName("timeStamp")
.setComparisonOperator("equals")
.setPropertyValueDate(eventFilterInput.getCdp_timestamp_equals())
.build());
}
if (eventFilterInput.getCdp_timestamp_gt() != null) {
subConditions.add(ConditionBuilder.builder(eventPropertyConditionType)
.setPropertyName("timeStamp")
.setComparisonOperator("greaterThan")
.setPropertyValueDate(eventFilterInput.getCdp_timestamp_gt())
.build());
}
if (eventFilterInput.getCdp_timestamp_gte() != null) {
subConditions.add(ConditionBuilder.builder(eventPropertyConditionType)
.setPropertyName("timeStamp")
.setComparisonOperator("greaterThanOrEqualTo")
.setPropertyValueDate(eventFilterInput.getCdp_timestamp_gte())
.build());
}
if (eventFilterInput.getCdp_timestamp_lt() != null) {
subConditions.add(ConditionBuilder.builder(eventPropertyConditionType)
.setPropertyName("timeStamp")
.setComparisonOperator("lessThan")
.setPropertyValueDate(eventFilterInput.getCdp_timestamp_lt())
.build());
}
if (eventFilterInput.getCdp_timestamp_lte() != null) {
subConditions.add(ConditionBuilder.builder(eventPropertyConditionType)
.setPropertyName("timeStamp")
.setComparisonOperator("lessThanOrEqualTo")
.setPropertyValueDate(eventFilterInput.getCdp_timestamp_lte()).build());
}
if (eventFilterInput.getCdp_consentUpdateEvent() != null) {
subConditions.add(createCdpConsentUpdateEventCondition(eventFilterInput.getCdp_consentUpdateEvent()));
}
if (eventFilterInput.getCdp_sessionEvent() != null) {
subConditions.add(createCdpSessionEventCondition(eventFilterInput.getCdp_sessionEvent()));
}
createConditionBasedOnFilterWithSubFilters(
subConditions, eventFilterInput.getAnd(), this::createEventPropertyCondition, "and");
createConditionBasedOnFilterWithSubFilters(
subConditions, eventFilterInput.getOr(), this::createEventPropertyCondition, "or");
return ConditionBuilder.builder(booleanConditionType).buildBooleanCondition("and", subConditions);
}
public Condition createCdpConsentUpdateEventCondition(final CDPConsentUpdateEventFilterInput eventFilterInput) {
final GraphQLInputObjectType inputObjectType =
(GraphQLInputObjectType) environment.getGraphQLSchema().getType(CDPConsentUpdateEventFilterInput.TYPE_NAME);
final List<Condition> subConditions = new ArrayList<>();
if (eventFilterInput.getStatus_equals() != null) {
subConditions.add(ConditionBuilder.builder(eventPropertyConditionType)
.setPropertyName("status")
.setComparisonOperator("equals")
.setPropertyValue(eventFilterInput.getStatus_equals()).build());
}
return ConditionBuilder.builder(booleanConditionType).buildBooleanCondition("and", subConditions);
}
public Condition createCdpSessionEventCondition(final CDPSessionEventFilterInput eventFilterInput) {
final List<Condition> subConditions = new ArrayList<>();
if (eventFilterInput.getState_equals() != null) {
subConditions.add(ConditionBuilder.builder(eventPropertyConditionType)
.setPropertyName("properties.state")
.setComparisonOperator("equals")
.setPropertyValue(eventFilterInput.getState_equals().name()).build());
}
if (eventFilterInput.getUnomi_scope_equals() != null) {
subConditions.add(ConditionBuilder.builder(eventPropertyConditionType)
.setPropertyName("properties.scope")
.setComparisonOperator("equals")
.setPropertyValue(eventFilterInput.getUnomi_scope_equals()).build());
}
if (eventFilterInput.getUnomi_sessionId_equals() != null) {
subConditions.add(ConditionBuilder.builder(eventPropertyConditionType)
.setPropertyName("properties.sessionId")
.setComparisonOperator("equals")
.setPropertyValue(eventFilterInput.getUnomi_sessionId_equals()).build());
}
return ConditionBuilder.builder(booleanConditionType).buildBooleanCondition("and", subConditions);
}
final List<Condition> subConditions = new ArrayList<>();
private Condition createProfileEventsCondition(final CDPProfileEventsFilterInput eventsFilterInput) {
if (eventsFilterInput == null || eventsFilterInput.getEventFilter() == null) {
return null;
}
final List<Condition> subConditions = new ArrayList<>();
createConditionBasedOnFilterWithSubFilters(
subConditions, eventsFilterInput.getAnd(), this::createProfileEventsCondition, "and");
createConditionBasedOnFilterWithSubFilters(
subConditions, eventsFilterInput.getOr(), this::createProfileEventsCondition, "or");
if (eventsFilterInput.getNot() != null) {
subConditions.add(ConditionBuilder.builder(notConditionType)
.setParameter("subCondition", createProfileEventsCondition(eventsFilterInput.getNot()))
.build());
}
final Condition eventCondition = createEventPropertyCondition(eventsFilterInput.getEventFilter());
final Condition pastEventCondition = ConditionBuilder.builder(pastEventConditionType)
.setParameter("minimumEventCount", eventsFilterInput.getMinimalCount())
.setParameter("maximumEventCount", eventsFilterInput.getMaximalCount())
.setParameter("eventCondition", eventCondition).build();
subConditions.add(pastEventCondition);
return ConditionBuilder.builder(booleanConditionType).buildBooleanCondition("and", subConditions);
}
private <INPUT> void createConditionBasedOnFilterWithSubFilters(
final List<Condition> container, final List<INPUT> inputFilters, final Function<INPUT, Condition> function, final String operator) {
if (inputFilters == null || inputFilters.isEmpty()) {
return;
}
final List<Condition> subConditions = inputFilters.stream()
.map(function)
.collect(Collectors.toList());
container.add(ConditionBuilder.builder(booleanConditionType).buildBooleanCondition(operator, subConditions));
}
private Condition createSegmentCondition() {
final List<Condition> subConditions = createSubCondition();
return ConditionBuilder.builder(booleanConditionType).buildBooleanCondition("and", subConditions);
}
public static Builder create(final CDPSegmentInput segmentInput, final DataFetchingEnvironment environment) {
return new Builder(segmentInput, environment);
}
public static class Builder extends BaseCommand.Builder<Builder> {
private final CDPSegmentInput segmentInput;
private final DataFetchingEnvironment environment;
public Builder(CDPSegmentInput segmentInput, DataFetchingEnvironment environment) {
this.segmentInput = segmentInput;
this.environment = environment;
}
private void validate() {
if (segmentInput == null) {
throw new IllegalArgumentException();
}
if (Strings.isNullOrEmpty(segmentInput.getName())) {
throw new IllegalArgumentException();
}
if (Strings.isNullOrEmpty(segmentInput.getView())) {
throw new IllegalArgumentException();
}
}
public CreateOrUpdateSegmentCommand build() {
validate();
return new CreateOrUpdateSegmentCommand(this);
}
}
}