diff --git a/pom.xml b/pom.xml index 31213b1ecce..5972285d55b 100644 --- a/pom.xml +++ b/pom.xml @@ -64,6 +64,7 @@ 7.70.0.Final netgrif-oss https://sonarcloud.io + 4.13.1 @@ -505,6 +506,14 @@ jackson-module-jsonSchema 2.13.2 + + + + org.antlr + antlr4-runtime + ${antlr4.version} + + @@ -779,6 +788,22 @@ license-maven-plugin 2.0.0 + + org.antlr + antlr4-maven-plugin + ${antlr4.version} + + src/main/java + ${project.build.directory}/generated-sources/java/ + + + + + antlr4 + + + + diff --git a/src/main/groovy/com/netgrif/application/engine/petrinet/domain/dataset/logic/action/ActionDelegate.groovy b/src/main/groovy/com/netgrif/application/engine/petrinet/domain/dataset/logic/action/ActionDelegate.groovy index f00157c40fa..065b70c23c4 100644 --- a/src/main/groovy/com/netgrif/application/engine/petrinet/domain/dataset/logic/action/ActionDelegate.groovy +++ b/src/main/groovy/com/netgrif/application/engine/petrinet/domain/dataset/logic/action/ActionDelegate.groovy @@ -32,6 +32,7 @@ import com.netgrif.application.engine.petrinet.domain.version.Version import com.netgrif.application.engine.petrinet.service.interfaces.IPetriNetService import com.netgrif.application.engine.petrinet.service.interfaces.IUriService import com.netgrif.application.engine.rules.domain.RuleRepository +import com.netgrif.application.engine.search.interfaces.ISearchService import com.netgrif.application.engine.startup.DefaultFiltersRunner import com.netgrif.application.engine.startup.FilterRunner import com.netgrif.application.engine.utils.FullPageRequest @@ -177,6 +178,9 @@ class ActionDelegate /*TODO: release/8.0.0: implements ActionAPI*/ { @Autowired PublicViewProperties publicViewProperties + @Autowired + ISearchService searchService + FrontendActionOutcome Frontend /** @@ -2321,4 +2325,20 @@ class ActionDelegate /*TODO: release/8.0.0: implements ActionAPI*/ { Case taskCase = workflowService.findOne(task.caseId) return taskCase.getPetriNet().getDataSet().get(fieldId) } + + String explainQuery(String query) { + return searchService.explainQuery(query) + } + + long count(String query) { + return searchService.count(query) + } + + def search(String query) { + return searchService.search(query) + } + + boolean exists(String query) { + return searchService.exists(query) + } } \ No newline at end of file diff --git a/src/main/java/com/netgrif/application/engine/elastic/domain/ElasticCase.java b/src/main/java/com/netgrif/application/engine/elastic/domain/ElasticCase.java index ebe113ea85c..49480de8bae 100644 --- a/src/main/java/com/netgrif/application/engine/elastic/domain/ElasticCase.java +++ b/src/main/java/com/netgrif/application/engine/elastic/domain/ElasticCase.java @@ -77,11 +77,9 @@ public class ElasticCase { private Map dataSet; - @Field(type = Keyword) - private Set taskIds; + private Map tasks; - @Field(type = Keyword) - private Set taskMongoIds; + private Map places; @Field(type = Keyword) private Set enabledRoles; @@ -126,8 +124,6 @@ public ElasticCase(Case useCase) { author = useCase.getAuthor().getId(); authorName = useCase.getAuthor().getFullName(); authorEmail = useCase.getAuthor().getEmail(); - taskIds = useCase.getTasks().keySet(); - taskMongoIds = useCase.getTasks().values().stream().map(TaskPair::getTaskStringId).collect(Collectors.toSet()); enabledRoles = new HashSet<>(useCase.getEnabledRoles()); viewRoles = new HashSet<>(useCase.getViewRoles()); viewUserRefs = new HashSet<>(useCase.getViewUserRefs()); @@ -137,6 +133,17 @@ public ElasticCase(Case useCase) { tags = new HashMap<>(useCase.getTags()); dataSet = new HashMap<>(); + + tasks = useCase.getTasks().entrySet().stream() + .collect(Collectors.toMap( + Map.Entry::getKey, + entry -> new Task(entry.getValue().getTaskStringId(), entry.getValue().getState(), entry.getValue().getUserId()) + )); + places = useCase.getActivePlaces().entrySet().stream() + .collect(Collectors.toMap( + Map.Entry::getKey, + entry -> new Place(entry.getValue()) + )); } public void update(ElasticCase useCase) { @@ -146,8 +153,6 @@ public void update(ElasticCase useCase) { uriNodeId = useCase.getUriNodeId(); } title = useCase.getTitle(); - taskIds = useCase.getTaskIds(); - taskMongoIds = useCase.getTaskMongoIds(); enabledRoles = useCase.getEnabledRoles(); viewRoles = useCase.getViewRoles(); viewUserRefs = useCase.getViewUserRefs(); @@ -157,5 +162,7 @@ public void update(ElasticCase useCase) { tags = useCase.getTags(); dataSet = useCase.getDataSet(); + tasks = useCase.getTasks(); + places = useCase.getPlaces(); } } \ No newline at end of file diff --git a/src/main/java/com/netgrif/application/engine/elastic/domain/MapField.java b/src/main/java/com/netgrif/application/engine/elastic/domain/MapField.java index 0e3d22a4317..e75fb41cd3e 100644 --- a/src/main/java/com/netgrif/application/engine/elastic/domain/MapField.java +++ b/src/main/java/com/netgrif/application/engine/elastic/domain/MapField.java @@ -1,14 +1,12 @@ package com.netgrif.application.engine.elastic.domain; +import com.netgrif.application.engine.petrinet.domain.I18nString; import lombok.Data; import lombok.EqualsAndHashCode; import lombok.NoArgsConstructor; import org.springframework.data.elasticsearch.annotations.Field; -import java.util.ArrayList; -import java.util.Collection; -import java.util.List; -import java.util.Map; +import java.util.*; import static org.springframework.data.elasticsearch.annotations.FieldType.Keyword; @@ -20,17 +18,21 @@ public class MapField extends TextField { @Field(type = Keyword) public List keyValue = new ArrayList<>(); - public MapField(String key, List values) { + public List options = new ArrayList<>(); + + public MapField(String key, List values, Map options) { super(values); this.keyValue.add(key); + this.options.addAll(options.keySet()); } - public MapField(Map> valuePairs) { + public MapField(Map> valuePairs, Map options) { super(); valuePairs.forEach((key, value) -> { this.keyValue.add(key); this.textValue.addAll(value); this.fulltextValue.addAll(value); }); + this.options.addAll(options.keySet()); } } diff --git a/src/main/java/com/netgrif/application/engine/elastic/domain/Place.java b/src/main/java/com/netgrif/application/engine/elastic/domain/Place.java new file mode 100644 index 00000000000..b6c14908bd1 --- /dev/null +++ b/src/main/java/com/netgrif/application/engine/elastic/domain/Place.java @@ -0,0 +1,21 @@ +package com.netgrif.application.engine.elastic.domain; + +import lombok.AllArgsConstructor; +import lombok.Data; +import lombok.EqualsAndHashCode; +import lombok.NoArgsConstructor; +import org.springframework.data.elasticsearch.annotations.Field; + +import static org.springframework.data.elasticsearch.annotations.FieldType.Integer; +import static org.springframework.data.elasticsearch.annotations.FieldType.Text; + +@Data +@NoArgsConstructor +@EqualsAndHashCode +@AllArgsConstructor +public class Place { + + @Field(type = Integer) + public Integer marking; + +} \ No newline at end of file diff --git a/src/main/java/com/netgrif/application/engine/elastic/domain/Task.java b/src/main/java/com/netgrif/application/engine/elastic/domain/Task.java new file mode 100644 index 00000000000..fecd15381ed --- /dev/null +++ b/src/main/java/com/netgrif/application/engine/elastic/domain/Task.java @@ -0,0 +1,27 @@ +package com.netgrif.application.engine.elastic.domain; + +import com.netgrif.application.engine.workflow.domain.State; +import lombok.AllArgsConstructor; +import lombok.Data; +import lombok.EqualsAndHashCode; +import lombok.NoArgsConstructor; +import org.springframework.data.elasticsearch.annotations.Field; + +import static org.springframework.data.elasticsearch.annotations.FieldType.Text; + +@Data +@NoArgsConstructor +@EqualsAndHashCode +@AllArgsConstructor +public class Task { + + @Field(type = Text) + public String stringId; + + @Field(type = Text) + public State state; + + @Field(type = Text) + public String userId; + +} \ No newline at end of file diff --git a/src/main/java/com/netgrif/application/engine/elastic/service/transform/EnumerationMapFieldTransformer.java b/src/main/java/com/netgrif/application/engine/elastic/service/transform/EnumerationMapFieldTransformer.java index d472da66020..393cb1f0709 100644 --- a/src/main/java/com/netgrif/application/engine/elastic/service/transform/EnumerationMapFieldTransformer.java +++ b/src/main/java/com/netgrif/application/engine/elastic/service/transform/EnumerationMapFieldTransformer.java @@ -18,7 +18,7 @@ public MapField transform(EnumerationMapField caseField, EnumerationMapField pet DataFieldValue selectedKey = caseField.getValue(); String value = selectedKey != null ? selectedKey.getValue() : null; I18nString selectedValue = options.get(value) != null ? options.get(value) : new I18nString(); - return new MapField(value, selectedValue.collectTranslations()); + return new MapField(value, selectedValue.collectTranslations(), caseField.getOptions()); } @Override diff --git a/src/main/java/com/netgrif/application/engine/elastic/service/transform/MultichoiceMapFieldTransformer.java b/src/main/java/com/netgrif/application/engine/elastic/service/transform/MultichoiceMapFieldTransformer.java index 80c4ea2bd6c..59948dc3123 100644 --- a/src/main/java/com/netgrif/application/engine/elastic/service/transform/MultichoiceMapFieldTransformer.java +++ b/src/main/java/com/netgrif/application/engine/elastic/service/transform/MultichoiceMapFieldTransformer.java @@ -25,7 +25,7 @@ public MapField transform(MultichoiceMapField caseField, MultichoiceMapField pet I18nString selectedValue = options.get(value) != null ? options.get(value) : new I18nString(); fieldValues.put(value, selectedValue.collectTranslations()); } - return new MapField(fieldValues); + return new MapField(fieldValues, options); } @Override diff --git a/src/main/java/com/netgrif/application/engine/search/QueryLangErrorListener.java b/src/main/java/com/netgrif/application/engine/search/QueryLangErrorListener.java new file mode 100644 index 00000000000..03057c23102 --- /dev/null +++ b/src/main/java/com/netgrif/application/engine/search/QueryLangErrorListener.java @@ -0,0 +1,36 @@ +package com.netgrif.application.engine.search; + +import lombok.Getter; +import org.antlr.v4.runtime.*; +import org.antlr.v4.runtime.misc.ParseCancellationException; + +import java.util.ArrayList; +import java.util.List; + +@Getter +public class QueryLangErrorListener extends BaseErrorListener { + List errorMessages = new ArrayList<>(); + + @Override + public void syntaxError(Recognizer recognizer, Object offendingSymbol, int line, int charPositionInLine, String msg, RecognitionException e) + throws ParseCancellationException { + errorMessages.add(underlineError(recognizer, (Token) offendingSymbol, line, charPositionInLine, msg)); + } + + protected String underlineError(Recognizer recognizer, Token offendingToken, int line, int charPositionInLine, String msg) { + String underlineErrorMsg = msg + "\n"; + int start = offendingToken.getStartIndex(); + int stop = offendingToken.getStopIndex(); + if (start > stop) { + return underlineErrorMsg; + } + CommonTokenStream tokens = (CommonTokenStream) recognizer.getInputStream(); + String input = tokens.getTokenSource().getInputStream().toString(); + String[] lines = input.split("\n"); + String errorLine = lines[line - 1]; + underlineErrorMsg += errorLine + "\n"; + underlineErrorMsg += " ".repeat(charPositionInLine) + "^".repeat(stop - start + 1) + "\n"; + + return underlineErrorMsg; + } +} diff --git a/src/main/java/com/netgrif/application/engine/search/QueryLangEvaluator.java b/src/main/java/com/netgrif/application/engine/search/QueryLangEvaluator.java new file mode 100644 index 00000000000..72ca6548115 --- /dev/null +++ b/src/main/java/com/netgrif/application/engine/search/QueryLangEvaluator.java @@ -0,0 +1,1422 @@ +package com.netgrif.application.engine.search; + +import com.netgrif.application.engine.search.antlr4.QueryLangBaseListener; +import com.netgrif.application.engine.search.antlr4.QueryLangParser; +import com.netgrif.application.engine.auth.domain.QUser; +import com.netgrif.application.engine.petrinet.domain.QPetriNet; +import com.netgrif.application.engine.search.enums.ComparisonType; +import com.netgrif.application.engine.search.enums.QueryType; +import com.netgrif.application.engine.workflow.domain.QCase; +import com.netgrif.application.engine.workflow.domain.QTask; +import com.netgrif.application.engine.workflow.domain.State; +import com.querydsl.core.BooleanBuilder; +import com.querydsl.core.types.Predicate; +import com.querydsl.core.types.dsl.DateTimePath; +import com.querydsl.core.types.dsl.StringPath; +import lombok.Getter; +import lombok.Setter; +import org.antlr.v4.runtime.Token; +import org.antlr.v4.runtime.tree.ParseTree; +import org.antlr.v4.runtime.tree.ParseTreeProperty; +import org.antlr.v4.runtime.tree.TerminalNode; +import org.bson.types.ObjectId; +import org.bson.types.QObjectId; +import org.springframework.data.domain.PageRequest; +import org.springframework.data.domain.Pageable; +import org.springframework.data.domain.Sort; + +import java.sql.Timestamp; +import java.time.LocalDateTime; +import java.util.*; +import java.util.stream.Collectors; + +import static com.netgrif.application.engine.search.utils.SearchUtils.*; + +public class QueryLangEvaluator extends QueryLangBaseListener { + + private final ParseTreeProperty elasticQuery = new ParseTreeProperty<>(); + private final ParseTreeProperty mongoQuery = new ParseTreeProperty<>(); + + @Getter + private QueryType type; + @Getter + private Boolean multiple; + @Getter + @Setter + private Boolean searchWithElastic = false; + @Getter + private Predicate fullMongoQuery; + @Getter + private String fullElasticQuery; + @Getter + private Pageable pageable; + + private int pageNumber = 0; + private int pageSize = 20; + private final List sortOrders = new ArrayList<>(); + + public void setElasticQuery(ParseTree node, String query) { + elasticQuery.put(node, query); + } + + public String getElasticQuery(ParseTree node) { + return elasticQuery.get(node); + } + + public void setMongoQuery(ParseTree node, Predicate predicate) { + mongoQuery.put(node, predicate); + } + + public Predicate getMongoQuery(ParseTree node) { + return mongoQuery.get(node); + } + + private void processBasicExpression(ParseTree child, ParseTree current) { + setMongoQuery(current, getMongoQuery(child)); + setElasticQuery(current, getElasticQuery(child)); + } + + private void processOrExpression(List children, ParseTree current) { + List predicates = children.stream() + .map(this::getMongoQuery) + .filter(Objects::nonNull) + .collect(Collectors.toList()); + String elasticQuery = children.stream() + .map(this::getElasticQuery) + .filter(Objects::nonNull) + .collect(Collectors.joining(" OR ")); + + if (!predicates.isEmpty()) { + BooleanBuilder predicate = new BooleanBuilder(); + predicates.forEach(predicate::or); + setMongoQuery(current, predicate); + } + setElasticQuery(current, elasticQuery.isBlank() ? null : elasticQuery); + } + + private void processAndExpression(List children, ParseTree current) { + List predicates = children.stream() + .map(this::getMongoQuery) + .filter(Objects::nonNull) + .collect(Collectors.toList()); + String elasticQuery = children.stream() + .map(this::getElasticQuery) + .filter(Objects::nonNull) + .collect(Collectors.joining(" AND ")); + + if (!predicates.isEmpty()) { + BooleanBuilder predicate = new BooleanBuilder(); + predicates.forEach(predicate::and); + setMongoQuery(current, predicate); + } + setElasticQuery(current, elasticQuery.isBlank() ? null : elasticQuery); + } + + private void processConditionGroup(ParseTree child, ParseTree current, Boolean not, Boolean parenthesis) { + Predicate predicate = getMongoQuery(child); + String elasticQuery = getElasticQuery(child); + + if (predicate != null) { + predicate = not ? (predicate).not() : (predicate); + } + + if (elasticQuery != null) { + if (parenthesis) { + elasticQuery = "(" + elasticQuery + ")"; + } + + if (not) { + elasticQuery = "NOT " + elasticQuery; + } + } + + setMongoQuery(current, predicate); + setElasticQuery(current, elasticQuery); + } + + @Override + public void enterProcessQuery(QueryLangParser.ProcessQueryContext ctx) { + type = QueryType.PROCESS; + multiple = ctx.resource.getType() == QueryLangParser.PROCESSES; + } + + @Override + public void exitProcessQuery(QueryLangParser.ProcessQueryContext ctx) { + processBasicExpression(ctx.processConditions(), ctx); + fullMongoQuery = getMongoQuery(ctx); + fullElasticQuery = getElasticQuery(ctx); + pageable = PageRequest.of(pageNumber, pageSize, Sort.by(sortOrders)); + } + + @Override + public void enterCaseQuery(QueryLangParser.CaseQueryContext ctx) { + type = QueryType.CASE; + multiple = ctx.resource.getType() == QueryLangParser.CASES; + } + + @Override + public void exitCaseQuery(QueryLangParser.CaseQueryContext ctx) { + processBasicExpression(ctx.caseConditions(), ctx); + fullMongoQuery = getMongoQuery(ctx); + fullElasticQuery = getElasticQuery(ctx); + pageable = PageRequest.of(pageNumber, pageSize, Sort.by(sortOrders)); + } + + @Override + public void enterTaskQuery(QueryLangParser.TaskQueryContext ctx) { + type = QueryType.TASK; + multiple = ctx.resource.getType() == QueryLangParser.TASKS; + } + + @Override + public void exitTaskQuery(QueryLangParser.TaskQueryContext ctx) { + processBasicExpression(ctx.taskConditions(), ctx); + fullMongoQuery = getMongoQuery(ctx); + fullElasticQuery = getElasticQuery(ctx); + pageable = PageRequest.of(pageNumber, pageSize, Sort.by(sortOrders)); + } + + @Override + public void enterUserQuery(QueryLangParser.UserQueryContext ctx) { + type = QueryType.USER; + multiple = ctx.resource.getType() == QueryLangParser.USERS; + } + + @Override + public void exitUserQuery(QueryLangParser.UserQueryContext ctx) { + processBasicExpression(ctx.userConditions(), ctx); + fullMongoQuery = getMongoQuery(ctx); + fullElasticQuery = getElasticQuery(ctx); + pageable = PageRequest.of(pageNumber, pageSize, Sort.by(sortOrders)); + } + + @Override + public void exitProcessConditions(QueryLangParser.ProcessConditionsContext ctx) { + processBasicExpression(ctx.processOrExpression(), ctx); + } + + @Override + public void exitProcessOrExpression(QueryLangParser.ProcessOrExpressionContext ctx) { + List children = ctx.processAndExpression().stream() + .map(andExpression -> (ParseTree) andExpression) + .collect(Collectors.toList()); + + processOrExpression(children, ctx); + } + + @Override + public void exitProcessAndExpression(QueryLangParser.ProcessAndExpressionContext ctx) { + List children = ctx.processConditionGroup().stream() + .map(conditionGroup -> (ParseTree) conditionGroup) + .collect(Collectors.toList()); + + processAndExpression(children, ctx); + } + + @Override + public void exitProcessConditionGroupBasic(QueryLangParser.ProcessConditionGroupBasicContext ctx) { + processConditionGroup(ctx.processCondition(), ctx, false, false); + } + + @Override + public void exitProcessConditionGroupParenthesis(QueryLangParser.ProcessConditionGroupParenthesisContext ctx) { + processConditionGroup(ctx.processConditions(), ctx, ctx.NOT() != null, true); + } + + @Override + public void exitProcessCondition(QueryLangParser.ProcessConditionContext ctx) { + processBasicExpression(ctx.processComparisons(), ctx); + } + + @Override + public void exitCaseConditions(QueryLangParser.CaseConditionsContext ctx) { + processBasicExpression(ctx.caseOrExpression(), ctx); + } + + @Override + public void exitCaseOrExpression(QueryLangParser.CaseOrExpressionContext ctx) { + List children = ctx.caseAndExpression().stream() + .map(andExpression -> (ParseTree) andExpression) + .collect(Collectors.toList()); + + processOrExpression(children, ctx); + } + + @Override + public void exitCaseAndExpression(QueryLangParser.CaseAndExpressionContext ctx) { + List children = ctx.caseConditionGroup().stream() + .map(conditionGroup -> (ParseTree) conditionGroup) + .collect(Collectors.toList()); + + processAndExpression(children, ctx); + } + + @Override + public void exitCaseConditionGroupBasic(QueryLangParser.CaseConditionGroupBasicContext ctx) { + processConditionGroup(ctx.caseCondition(), ctx, false, false); + } + + @Override + public void exitCaseConditionGroupParenthesis(QueryLangParser.CaseConditionGroupParenthesisContext ctx) { + processConditionGroup(ctx.caseConditions(), ctx, ctx.NOT() != null, true); + } + + @Override + public void exitCaseCondition(QueryLangParser.CaseConditionContext ctx) { + processBasicExpression(ctx.caseComparisons(), ctx); + } + + @Override + public void exitTaskConditions(QueryLangParser.TaskConditionsContext ctx) { + processBasicExpression(ctx.taskOrExpression(), ctx); + } + + @Override + public void exitTaskOrExpression(QueryLangParser.TaskOrExpressionContext ctx) { + List children = ctx.taskAndExpression().stream() + .map(andExpression -> (ParseTree) andExpression) + .collect(Collectors.toList()); + + processOrExpression(children, ctx); + } + + @Override + public void exitTaskAndExpression(QueryLangParser.TaskAndExpressionContext ctx) { + List children = ctx.taskConditionGroup().stream() + .map(conditionGroup -> (ParseTree) conditionGroup) + .collect(Collectors.toList()); + + processAndExpression(children, ctx); + } + + @Override + public void exitTaskConditionGroupBasic(QueryLangParser.TaskConditionGroupBasicContext ctx) { + processConditionGroup(ctx.taskCondition(), ctx, false, false); + } + + @Override + public void exitTaskConditionGroupParenthesis(QueryLangParser.TaskConditionGroupParenthesisContext ctx) { + processConditionGroup(ctx.taskConditions(), ctx, ctx.NOT() != null, true); + } + + @Override + public void exitTaskCondition(QueryLangParser.TaskConditionContext ctx) { + processBasicExpression(ctx.taskComparisons(), ctx); + } + + @Override + public void exitUserConditions(QueryLangParser.UserConditionsContext ctx) { + processBasicExpression(ctx.userOrExpression(), ctx); + } + + @Override + public void exitUserOrExpression(QueryLangParser.UserOrExpressionContext ctx) { + List children = ctx.userAndExpression().stream() + .map(andExpression -> (ParseTree) andExpression) + .collect(Collectors.toList()); + + processOrExpression(children, ctx); + } + + @Override + public void exitUserAndExpression(QueryLangParser.UserAndExpressionContext ctx) { + List children = ctx.userConditionGroup().stream() + .map(conditionGroup -> (ParseTree) conditionGroup) + .collect(Collectors.toList()); + + processAndExpression(children, ctx); + } + + @Override + public void exitUserConditionGroupBasic(QueryLangParser.UserConditionGroupBasicContext ctx) { + processConditionGroup(ctx.userCondition(), ctx, false, false); + } + + @Override + public void exitUserConditionGroupParenthesis(QueryLangParser.UserConditionGroupParenthesisContext ctx) { + processConditionGroup(ctx.userConditions(), ctx, ctx.NOT() != null, true); + } + + @Override + public void exitUserCondition(QueryLangParser.UserConditionContext ctx) { + processBasicExpression(ctx.userComparisons(), ctx); + } + + @Override + public void exitProcessComparisons(QueryLangParser.ProcessComparisonsContext ctx) { + processBasicExpression(ctx.children.get(0), ctx); + } + + @Override + public void exitCaseComparisons(QueryLangParser.CaseComparisonsContext ctx) { + processBasicExpression(ctx.children.get(0), ctx); + } + + @Override + public void exitTaskComparisons(QueryLangParser.TaskComparisonsContext ctx) { + processBasicExpression(ctx.children.get(0), ctx); + } + + @Override + public void exitUserComparisons(QueryLangParser.UserComparisonsContext ctx) { + processBasicExpression(ctx.children.get(0), ctx); + } + + @Override + public void exitIdBasic(QueryLangParser.IdBasicContext ctx) { + QObjectId qObjectId; + Token op = ctx.objectIdComparison().op; + boolean not = ctx.objectIdComparison().NOT() != null; + checkOp(ComparisonType.ID, op); + ObjectId objectId = getObjectIdValue(ctx.objectIdComparison().STRING().getText()); + + switch (type) { + case PROCESS: + qObjectId = QPetriNet.petriNet.id; + break; + case CASE: + qObjectId = QCase.case$.id; + setElasticQuery(ctx, buildElasticQuery("stringId", op.getType(), objectId.toString(), not)); + break; + case TASK: + qObjectId = QTask.task.id; + break; + case USER: + qObjectId = QUser.user.id; + break; + default: + throw new IllegalArgumentException("Unknown query type: " + type); + } + + setMongoQuery(ctx, buildObjectIdPredicate(qObjectId, op.getType(), objectId, not)); + } + + @Override + public void exitIdList(QueryLangParser.IdListContext ctx) { + QObjectId qObjectId; + Token op = ctx.inListStringComparison().op; + boolean not = ctx.inListStringComparison().NOT() != null; + checkOp(ComparisonType.ID, op); + List objectIdList = ctx.inListStringComparison().stringList().STRING().stream() + .map(node -> getObjectIdValue(node.getText())) + .collect(Collectors.toList()); + List stringIdList = ctx.inListStringComparison().stringList().STRING().stream() + .map(node -> getStringValue(node.getText())) + .collect(Collectors.toList()); + + switch (type) { + case PROCESS: + qObjectId = QPetriNet.petriNet.id; + break; + case CASE: + qObjectId = QCase.case$.id; + setElasticQuery(ctx, buildElasticQueryInList("stringId", stringIdList, not)); + break; + case TASK: + qObjectId = QTask.task.id; + break; + case USER: + qObjectId = QUser.user.id; + break; + default: + throw new IllegalArgumentException("Unknown query type: " + type); + } + + setMongoQuery(ctx, buildObjectIdPredicateInList(qObjectId, objectIdList, not)); + } + + @Override + public void exitTitleBasic(QueryLangParser.TitleBasicContext ctx) { + StringPath stringPath; + Token op = ctx.stringComparison().op; + boolean not = ctx.stringComparison().NOT() != null; + String string = getStringValue(ctx.stringComparison().STRING().getText()); + + switch (type) { + case PROCESS: + stringPath = QPetriNet.petriNet.title.defaultValue; + break; + case CASE: + stringPath = QCase.case$.title; + setElasticQuery(ctx, buildElasticQuery("title", op.getType(), string, not)); + break; + case TASK: + stringPath = QTask.task.title.defaultValue; + break; + default: + throw new IllegalArgumentException("Unknown query type: " + type); + } + + setMongoQuery(ctx, buildStringPredicate(stringPath, op.getType(), string, not)); + } + + @Override + public void exitTitleList(QueryLangParser.TitleListContext ctx) { + StringPath stringPath; + boolean not = ctx.inListStringComparison().NOT() != null; + List stringList = ctx.inListStringComparison().stringList().STRING().stream().map(node -> getStringValue(node.getText())).collect(Collectors.toList()); + + switch (type) { + case PROCESS: + stringPath = QPetriNet.petriNet.title.defaultValue; + break; + case CASE: + stringPath = QCase.case$.title; + setElasticQuery(ctx, buildElasticQueryInList("title", stringList, not)); + break; + case TASK: + stringPath = QTask.task.title.defaultValue; + break; + default: + throw new IllegalArgumentException("Unknown query type: " + type); + } + + setMongoQuery(ctx, buildStringPredicateInList(stringPath, stringList, not)); + } + + @Override + public void exitTitleRange(QueryLangParser.TitleRangeContext ctx) { + StringPath stringPath; + boolean not = ctx.inRangeStringComparison().NOT() != null; + boolean leftEndpointOpen = ctx.inRangeStringComparison().stringRange().leftEndpoint.getText().equals(LEFT_OPEN_ENDPOINT); + boolean rightEndpointOpen = ctx.inRangeStringComparison().stringRange().rightEndpoint.getText().equals(RIGHT_OPEN_ENDPOINT); + String leftString = getStringValue(ctx.inRangeStringComparison().stringRange().STRING(0).getText()); + String rightString = getStringValue(ctx.inRangeStringComparison().stringRange().STRING(1).getText()); + + switch (type) { + case PROCESS: + stringPath = QPetriNet.petriNet.title.defaultValue; + break; + case CASE: + stringPath = QCase.case$.title; + setElasticQuery(ctx, buildElasticQueryInRange("title", leftString, leftEndpointOpen, rightString, rightEndpointOpen, not)); + break; + case TASK: + stringPath = QTask.task.title.defaultValue; + break; + default: + throw new IllegalArgumentException("Unknown query type: " + type); + } + + setMongoQuery(ctx, buildStringPredicateInRange(stringPath, leftString, leftEndpointOpen, rightString, rightEndpointOpen, not)); + } + + @Override + public void exitIdentifierBasic(QueryLangParser.IdentifierBasicContext ctx) { + StringPath stringPath = QPetriNet.petriNet.identifier; + Token op = ctx.stringComparison().op; + boolean not = ctx.stringComparison().NOT() != null; + String string = getStringValue(ctx.stringComparison().STRING().getText()); + + setMongoQuery(ctx, buildStringPredicate(stringPath, op.getType(), string, not)); + } + + @Override + public void exitIdentifierList(QueryLangParser.IdentifierListContext ctx) { + StringPath stringPath = QPetriNet.petriNet.identifier; + boolean not = ctx.inListStringComparison().NOT() != null; + List stringList = ctx.inListStringComparison().stringList().STRING().stream().map(node -> getStringValue(node.getText())).collect(Collectors.toList()); + + setMongoQuery(ctx, buildStringPredicateInList(stringPath, stringList, not)); + } + + @Override + public void exitIdentifierRange(QueryLangParser.IdentifierRangeContext ctx) { + StringPath stringPath = QPetriNet.petriNet.identifier; + boolean not = ctx.inRangeStringComparison().NOT() != null; + boolean leftEndpointOpen = ctx.inRangeStringComparison().stringRange().leftEndpoint.getText().equals(LEFT_OPEN_ENDPOINT); + boolean rightEndpointOpen = ctx.inRangeStringComparison().stringRange().rightEndpoint.getText().equals(RIGHT_OPEN_ENDPOINT); + String leftString = getStringValue(ctx.inRangeStringComparison().stringRange().STRING(0).getText()); + String rightString = getStringValue(ctx.inRangeStringComparison().stringRange().STRING(1).getText()); + + setMongoQuery(ctx, buildStringPredicateInRange(stringPath, leftString, leftEndpointOpen, rightString, rightEndpointOpen, not)); + } + + @Override + public void exitVersionBasic(QueryLangParser.VersionBasicContext ctx) { + Token op = ctx.op; + boolean not = ctx.NOT() != null; + String versionString = ctx.VERSION_NUMBER().getText(); + + setMongoQuery(ctx, buildVersionPredicate(op.getType(), versionString, not)); + } + + @Override + public void exitVersionListCmp(QueryLangParser.VersionListCmpContext ctx) { + boolean not = ctx.inListVersionComparison().NOT() != null; + List stringList = ctx.inListVersionComparison().versionList().VERSION_NUMBER().stream().map(TerminalNode::getText).collect(Collectors.toList()); + + setMongoQuery(ctx, buildVersionPredicateInList(stringList, not)); + } + + @Override + public void exitVersionRangeCmp(QueryLangParser.VersionRangeCmpContext ctx) { + boolean not = ctx.inRangeVersionComparison().NOT() != null; + boolean leftEndpointOpen = ctx.inRangeVersionComparison().versionRange().leftEndpoint.getText().equals(LEFT_OPEN_ENDPOINT); + boolean rightEndpointOpen = ctx.inRangeVersionComparison().versionRange().rightEndpoint.getText().equals(RIGHT_OPEN_ENDPOINT); + String leftString = getStringValue(ctx.inRangeVersionComparison().versionRange().VERSION_NUMBER(0).getText()); + String rightString = getStringValue(ctx.inRangeVersionComparison().versionRange().VERSION_NUMBER(1).getText()); + + setMongoQuery(ctx, buildVersionPredicateInRange(leftString, leftEndpointOpen, rightString, rightEndpointOpen, not)); + } + + @Override + public void exitCdDateBasic(QueryLangParser.CdDateBasicContext ctx) { + DateTimePath dateTimePath; + Token op = ctx.dateComparison().op; + boolean not = ctx.dateComparison().NOT() != null; + LocalDateTime localDateTime = toDateTime(ctx.dateComparison().DATE().getText()); + + switch (type) { + case PROCESS: + dateTimePath = QPetriNet.petriNet.creationDate; + break; + case CASE: + dateTimePath = QCase.case$.creationDate; + setElasticQuery(ctx, buildElasticQuery("creationDateSortable", op.getType(), String.valueOf(Timestamp.valueOf(localDateTime).getTime()), not)); + break; + default: + throw new IllegalArgumentException("Unknown query type: " + type); + } + + setMongoQuery(ctx, buildDateTimePredicate(dateTimePath, op.getType(), localDateTime, not)); + } + + @Override + public void exitCdDateTimeBasic(QueryLangParser.CdDateTimeBasicContext ctx) { + DateTimePath dateTimePath; + Token op = ctx.dateTimeComparison().op; + boolean not = ctx.dateTimeComparison().NOT() != null; + LocalDateTime localDateTime = toDateTime(ctx.dateTimeComparison().DATETIME().getText()); + + switch (type) { + case PROCESS: + dateTimePath = QPetriNet.petriNet.creationDate; + break; + case CASE: + dateTimePath = QCase.case$.creationDate; + setElasticQuery(ctx, buildElasticQuery("creationDateSortable", op.getType(), String.valueOf(Timestamp.valueOf(localDateTime).getTime()), not)); + break; + default: + throw new IllegalArgumentException("Unknown query type: " + type); + } + + setMongoQuery(ctx, buildDateTimePredicate(dateTimePath, op.getType(), localDateTime, not)); + } + + @Override + public void exitCdDateList(QueryLangParser.CdDateListContext ctx) { + DateTimePath dateTimePath; + boolean not = ctx.inListDateComparison().NOT() != null; + List terminalNodeList = ctx.inListDateComparison().dateList() != null ? ctx.inListDateComparison().dateList().DATE() : ctx.inListDateComparison().dateTimeList().DATETIME() ; + List stringDateList = terminalNodeList.stream().map(TerminalNode::getText).collect(Collectors.toList()); + + switch (type) { + case PROCESS: + dateTimePath = QPetriNet.petriNet.creationDate; + break; + case CASE: + dateTimePath = QCase.case$.creationDate; + List timestampStringList = stringDateList.stream().map(dateString -> { + LocalDateTime localDateTime = toDateTime(dateString); + return String.valueOf(Timestamp.valueOf(localDateTime).getTime()); + }).collect(Collectors.toList()); + setElasticQuery(ctx, buildElasticQueryInList("creationDateSortable", timestampStringList, not)); + break; + default: + throw new IllegalArgumentException("Unknown query type: " + type); + } + + setMongoQuery(ctx, buildDateTimePredicateInList(dateTimePath, stringDateList, not)); + } + + @Override + public void exitCdDateRange(QueryLangParser.CdDateRangeContext ctx) { + DateTimePath dateTimePath; + boolean not = ctx.inRangeDateComparison().NOT() != null; + boolean leftEndpointOpen; + boolean rightEndpointOpen; + LocalDateTime leftDateTime; + LocalDateTime rightDateTime; + if (ctx.inRangeDateComparison().dateRange() != null) { + leftEndpointOpen = ctx.inRangeDateComparison().dateRange().leftEndpoint.getText().equals(LEFT_OPEN_ENDPOINT); + rightEndpointOpen = ctx.inRangeDateComparison().dateRange().rightEndpoint.getText().equals(RIGHT_OPEN_ENDPOINT); + leftDateTime = toDateTime(ctx.inRangeDateComparison().dateRange().DATE(0).getText()); + rightDateTime = toDateTime(ctx.inRangeDateComparison().dateRange().DATE(1).getText()); + } else { + leftEndpointOpen = ctx.inRangeDateComparison().dateTimeRange().leftEndpoint.getText().equals(LEFT_OPEN_ENDPOINT); + rightEndpointOpen = ctx.inRangeDateComparison().dateTimeRange().rightEndpoint.getText().equals(RIGHT_OPEN_ENDPOINT); + leftDateTime = toDateTime(ctx.inRangeDateComparison().dateTimeRange().DATETIME(0).getText()); + rightDateTime = toDateTime(ctx.inRangeDateComparison().dateTimeRange().DATETIME(1).getText()); + } + + + switch (type) { + case PROCESS: + dateTimePath = QPetriNet.petriNet.creationDate; + break; + case CASE: + dateTimePath = QCase.case$.creationDate; + setElasticQuery(ctx, buildElasticQueryInRange("creationDateSortable", String.valueOf(Timestamp.valueOf(leftDateTime).getTime()), leftEndpointOpen, String.valueOf(Timestamp.valueOf(rightDateTime).getTime()), rightEndpointOpen, not)); + break; + default: + throw new IllegalArgumentException("Unknown query type: " + type); + } + + setMongoQuery(ctx, buildDateTimePredicateInRange(dateTimePath, leftDateTime, leftEndpointOpen, rightDateTime, rightEndpointOpen, not)); + } + + @Override + public void exitProcessIdBasic(QueryLangParser.ProcessIdBasicContext ctx) { + StringPath stringPath = QTask.task.processId; + Token op = ctx.stringComparison().op; + boolean not = ctx.stringComparison().NOT() != null; + String string = getStringValue(ctx.stringComparison().STRING().getText()); + + setMongoQuery(ctx, buildStringPredicate(stringPath, op.getType(), string, not)); + } + + @Override + public void exitProcessIdList(QueryLangParser.ProcessIdListContext ctx) { + StringPath stringPath = QTask.task.processId; + boolean not = ctx.inListStringComparison().NOT() != null; + List stringList = ctx.inListStringComparison().stringList().STRING().stream() + .map(node -> getStringValue(node.getText())) + .collect(Collectors.toList()); + + setMongoQuery(ctx, buildStringPredicateInList(stringPath, stringList, not)); + } + + @Override + public void exitProcessIdObjIdBasic(QueryLangParser.ProcessIdObjIdBasicContext ctx) { + QObjectId qObjectId = QCase.case$.petriNetObjectId; + Token op = ctx.objectIdComparison().op; + boolean not = ctx.objectIdComparison().NOT() != null; + ObjectId objectId = getObjectIdValue(ctx.objectIdComparison().STRING().getText()); + + setMongoQuery(ctx, buildObjectIdPredicate(qObjectId, op.getType(), objectId, not)); + setElasticQuery(ctx, buildElasticQuery("processId", op.getType(), objectId.toString(), not)); + } + + @Override + public void exitProcessIdObjIdList(QueryLangParser.ProcessIdObjIdListContext ctx) { + QObjectId qObjectId = QCase.case$.petriNetObjectId; + boolean not = ctx.inListStringComparison().NOT() != null; + List objectIdList = ctx.inListStringComparison().stringList().STRING().stream() + .map(node -> getObjectIdValue(node.getText())) + .collect(Collectors.toList()); + List stringList = ctx.inListStringComparison().stringList().STRING().stream() + .map(node -> getStringValue(node.getText())) + .collect(Collectors.toList()); + + setMongoQuery(ctx, buildObjectIdPredicateInList(qObjectId, objectIdList, not)); + setElasticQuery(ctx, buildElasticQueryInList("processId", stringList, not)); + } + + @Override + public void exitProcessIdentifierBasic(QueryLangParser.ProcessIdentifierBasicContext ctx) { + StringPath stringPath = QCase.case$.processIdentifier; + Token op = ctx.stringComparison().op; + boolean not = ctx.stringComparison().NOT() != null; + String string = getStringValue(ctx.stringComparison().STRING().getText()); + + setMongoQuery(ctx, buildStringPredicate(stringPath, op.getType(), string, not)); + setElasticQuery(ctx, buildElasticQuery("processIdentifier", op.getType(), string, not)); + } + + @Override + public void exitProcessIdentifierList(QueryLangParser.ProcessIdentifierListContext ctx) { + StringPath stringPath = QCase.case$.processIdentifier; + boolean not = ctx.inListStringComparison().NOT() != null; + List stringList = ctx.inListStringComparison().stringList().STRING().stream().map(node -> getStringValue(node.getText())).collect(Collectors.toList()); + + setMongoQuery(ctx, buildStringPredicateInList(stringPath, stringList, not)); + setElasticQuery(ctx, buildElasticQueryInList("processIdentifier", stringList, not)); + } + + @Override + public void exitProcessIdentifierRange(QueryLangParser.ProcessIdentifierRangeContext ctx) { + StringPath stringPath = QCase.case$.processIdentifier; + boolean not = ctx.inRangeStringComparison().NOT() != null; + boolean leftEndpointOpen = ctx.inRangeStringComparison().stringRange().leftEndpoint.getText().equals(LEFT_OPEN_ENDPOINT); + boolean rightEndpointOpen = ctx.inRangeStringComparison().stringRange().rightEndpoint.getText().equals(RIGHT_OPEN_ENDPOINT); + String leftString = getStringValue(ctx.inRangeStringComparison().stringRange().STRING(0).getText()); + String rightString = getStringValue(ctx.inRangeStringComparison().stringRange().STRING(1).getText()); + + setMongoQuery(ctx, buildStringPredicateInRange(stringPath, leftString, leftEndpointOpen, rightString, rightEndpointOpen, not)); + setElasticQuery(ctx, buildElasticQueryInRange("processIdentifier", leftString, leftEndpointOpen, rightString, rightEndpointOpen, not)); + } + + @Override + public void exitAuthorBasic(QueryLangParser.AuthorBasicContext ctx) { + StringPath stringPath = QCase.case$.author.id; + Token op = ctx.stringComparison().op; + boolean not = ctx.stringComparison().NOT() != null; + String string = getStringValue(ctx.stringComparison().STRING().getText()); + + setMongoQuery(ctx, buildStringPredicate(stringPath, op.getType(), string, not)); + setElasticQuery(ctx, buildElasticQuery("author", op.getType(), string, not)); + } + + @Override + public void exitAuthorList(QueryLangParser.AuthorListContext ctx) { + StringPath stringPath = QCase.case$.author.id; + boolean not = ctx.inListStringComparison().NOT() != null; + List stringList = ctx.inListStringComparison().stringList().STRING().stream() + .map(node -> getStringValue(node.getText())) + .collect(Collectors.toList()); + + setMongoQuery(ctx, buildStringPredicateInList(stringPath, stringList, not)); + setElasticQuery(ctx, buildElasticQueryInList("author", stringList, not)); + } + + @Override + public void exitTransitionIdBasic(QueryLangParser.TransitionIdBasicContext ctx) { + StringPath stringPath = QTask.task.transitionId; + Token op = ctx.stringComparison().op; + boolean not = ctx.stringComparison().NOT() != null; + String string = getStringValue(ctx.stringComparison().STRING().getText()); + + setMongoQuery(ctx, buildStringPredicate(stringPath, op.getType(), string, not)); + } + + @Override + public void exitTransitionIdList(QueryLangParser.TransitionIdListContext ctx) { + StringPath stringPath = QTask.task.transitionId; + boolean not = ctx.inListStringComparison().NOT() != null; + List stringList = ctx.inListStringComparison().stringList().STRING().stream().map(node -> getStringValue(node.getText())).collect(Collectors.toList()); + + setMongoQuery(ctx, buildStringPredicateInList(stringPath, stringList, not)); + } + + @Override + public void exitTransitionIdRange(QueryLangParser.TransitionIdRangeContext ctx) { + StringPath stringPath = QTask.task.transitionId; + boolean not = ctx.inRangeStringComparison().NOT() != null; + boolean leftEndpointOpen = ctx.inRangeStringComparison().stringRange().leftEndpoint.getText().equals(LEFT_OPEN_ENDPOINT); + boolean rightEndpointOpen = ctx.inRangeStringComparison().stringRange().rightEndpoint.getText().equals(RIGHT_OPEN_ENDPOINT); + String leftString = getStringValue(ctx.inRangeStringComparison().stringRange().STRING(0).getText()); + String rightString = getStringValue(ctx.inRangeStringComparison().stringRange().STRING(1).getText()); + + setMongoQuery(ctx, buildStringPredicateInRange(stringPath, leftString, leftEndpointOpen, rightString, rightEndpointOpen, not)); + } + + @Override + public void exitStateComparison(QueryLangParser.StateComparisonContext ctx) { + switch (ctx.state.getType()) { + case QueryLangParser.ENABLED: + setMongoQuery(ctx, QTask.task.state.eq(State.ENABLED)); + break; + case QueryLangParser.DISABLED: + setMongoQuery(ctx, QTask.task.state.eq(State.DISABLED)); + break; + } + } + + @Override + public void exitUserIdBasic(QueryLangParser.UserIdBasicContext ctx) { + StringPath stringPath = QTask.task.userId; + Token op = ctx.stringComparison().op; + boolean not = ctx.stringComparison().NOT() != null; + String string = getStringValue(ctx.stringComparison().STRING().getText()); + + setMongoQuery(ctx, buildStringPredicate(stringPath, op.getType(), string, not)); + } + + @Override + public void exitUserIdList(QueryLangParser.UserIdListContext ctx) { + StringPath stringPath = QTask.task.userId; + boolean not = ctx.inListStringComparison().NOT() != null; + List stringList = ctx.inListStringComparison().stringList().STRING().stream() + .map(node -> getStringValue(node.getText())) + .collect(Collectors.toList()); + + setMongoQuery(ctx, buildStringPredicateInList(stringPath, stringList, not)); + } + + @Override + public void exitCaseIdBasic(QueryLangParser.CaseIdBasicContext ctx) { + StringPath stringPath = QTask.task.caseId; + Token op = ctx.stringComparison().op; + boolean not = ctx.stringComparison().NOT() != null; + String string = getStringValue(ctx.stringComparison().STRING().getText()); + + setMongoQuery(ctx, buildStringPredicate(stringPath, op.getType(), string, not)); + } + + @Override + public void exitCaseIdList(QueryLangParser.CaseIdListContext ctx) { + StringPath stringPath = QTask.task.caseId; + boolean not = ctx.inListStringComparison().NOT() != null; + List stringList = ctx.inListStringComparison().stringList().STRING().stream() + .map(node -> getStringValue(node.getText())) + .collect(Collectors.toList()); + + setMongoQuery(ctx, buildStringPredicateInList(stringPath, stringList, not)); + } + + @Override + public void exitLaDateBasic(QueryLangParser.LaDateBasicContext ctx) { + DateTimePath dateTimePath = QTask.task.lastAssigned; + Token op = ctx.dateComparison().op; + boolean not = ctx.dateComparison().NOT() != null; + LocalDateTime localDateTime = toDateTime(ctx.dateComparison().DATE().getText()); + + setMongoQuery(ctx, buildDateTimePredicate(dateTimePath, op.getType(), localDateTime, not)); + } + + @Override + public void exitLaDateTimeBasic(QueryLangParser.LaDateTimeBasicContext ctx) { + DateTimePath dateTimePath = QTask.task.lastAssigned; + Token op = ctx.dateTimeComparison().op; + boolean not = ctx.dateTimeComparison().NOT() != null; + LocalDateTime localDateTime = toDateTime(ctx.dateTimeComparison().DATETIME().getText()); + + setMongoQuery(ctx, buildDateTimePredicate(dateTimePath, op.getType(), localDateTime, not)); + } + + @Override + public void exitLaDateList(QueryLangParser.LaDateListContext ctx) { + DateTimePath dateTimePath = QTask.task.lastAssigned; + boolean not = ctx.inListDateComparison().NOT() != null; + List terminalNodeList = ctx.inListDateComparison().dateList() != null ? ctx.inListDateComparison().dateList().DATE() : ctx.inListDateComparison().dateTimeList().DATETIME() ; + List stringDateList = terminalNodeList.stream().map(TerminalNode::getText).collect(Collectors.toList()); + + setMongoQuery(ctx, buildDateTimePredicateInList(dateTimePath, stringDateList, not)); + } + + @Override + public void exitLaDateRange(QueryLangParser.LaDateRangeContext ctx) { + DateTimePath dateTimePath = QTask.task.lastAssigned; + boolean not = ctx.inRangeDateComparison().NOT() != null; + boolean leftEndpointOpen; + boolean rightEndpointOpen; + LocalDateTime leftDateTime; + LocalDateTime rightDateTime; + if (ctx.inRangeDateComparison().dateRange() != null) { + leftEndpointOpen = ctx.inRangeDateComparison().dateRange().leftEndpoint.getText().equals(LEFT_OPEN_ENDPOINT); + rightEndpointOpen = ctx.inRangeDateComparison().dateRange().rightEndpoint.getText().equals(RIGHT_OPEN_ENDPOINT); + leftDateTime = toDateTime(ctx.inRangeDateComparison().dateRange().DATE(0).getText()); + rightDateTime = toDateTime(ctx.inRangeDateComparison().dateRange().DATE(1).getText()); + } else { + leftEndpointOpen = ctx.inRangeDateComparison().dateTimeRange().leftEndpoint.getText().equals(LEFT_OPEN_ENDPOINT); + rightEndpointOpen = ctx.inRangeDateComparison().dateTimeRange().rightEndpoint.getText().equals(RIGHT_OPEN_ENDPOINT); + leftDateTime = toDateTime(ctx.inRangeDateComparison().dateTimeRange().DATETIME(0).getText()); + rightDateTime = toDateTime(ctx.inRangeDateComparison().dateTimeRange().DATETIME(1).getText()); + } + + setMongoQuery(ctx, buildDateTimePredicateInRange(dateTimePath, leftDateTime, leftEndpointOpen, rightDateTime, rightEndpointOpen, not)); + } + + @Override + public void exitLfDateBasic(QueryLangParser.LfDateBasicContext ctx) { + DateTimePath dateTimePath = QTask.task.lastFinished; + Token op = ctx.dateComparison().op; + boolean not = ctx.dateComparison().NOT() != null; + LocalDateTime localDateTime = toDateTime(ctx.dateComparison().DATE().getText()); + + setMongoQuery(ctx, buildDateTimePredicate(dateTimePath, op.getType(), localDateTime, not)); + } + + @Override + public void exitLfDateTimeBasic(QueryLangParser.LfDateTimeBasicContext ctx) { + DateTimePath dateTimePath = QTask.task.lastFinished; + Token op = ctx.dateTimeComparison().op; + boolean not = ctx.dateTimeComparison().NOT() != null; + LocalDateTime localDateTime = toDateTime(ctx.dateTimeComparison().DATETIME().getText()); + + setMongoQuery(ctx, buildDateTimePredicate(dateTimePath, op.getType(), localDateTime, not)); + } + + @Override + public void exitLfDateList(QueryLangParser.LfDateListContext ctx) { + DateTimePath dateTimePath = QTask.task.lastFinished; + boolean not = ctx.inListDateComparison().NOT() != null; + List terminalNodeList = ctx.inListDateComparison().dateList() != null ? ctx.inListDateComparison().dateList().DATE() : ctx.inListDateComparison().dateTimeList().DATETIME() ; + List stringDateList = terminalNodeList.stream().map(TerminalNode::getText).collect(Collectors.toList()); + + setMongoQuery(ctx, buildDateTimePredicateInList(dateTimePath, stringDateList, not)); + } + + @Override + public void exitLfDateRange(QueryLangParser.LfDateRangeContext ctx) { + DateTimePath dateTimePath = QTask.task.lastFinished; + boolean not = ctx.inRangeDateComparison().NOT() != null; + boolean leftEndpointOpen; + boolean rightEndpointOpen; + LocalDateTime leftDateTime; + LocalDateTime rightDateTime; + if (ctx.inRangeDateComparison().dateRange() != null) { + leftEndpointOpen = ctx.inRangeDateComparison().dateRange().leftEndpoint.getText().equals(LEFT_OPEN_ENDPOINT); + rightEndpointOpen = ctx.inRangeDateComparison().dateRange().rightEndpoint.getText().equals(RIGHT_OPEN_ENDPOINT); + leftDateTime = toDateTime(ctx.inRangeDateComparison().dateRange().DATE(0).getText()); + rightDateTime = toDateTime(ctx.inRangeDateComparison().dateRange().DATE(1).getText()); + } else { + leftEndpointOpen = ctx.inRangeDateComparison().dateTimeRange().leftEndpoint.getText().equals(LEFT_OPEN_ENDPOINT); + rightEndpointOpen = ctx.inRangeDateComparison().dateTimeRange().rightEndpoint.getText().equals(RIGHT_OPEN_ENDPOINT); + leftDateTime = toDateTime(ctx.inRangeDateComparison().dateTimeRange().DATETIME(0).getText()); + rightDateTime = toDateTime(ctx.inRangeDateComparison().dateTimeRange().DATETIME(1).getText()); + } + + setMongoQuery(ctx, buildDateTimePredicateInRange(dateTimePath, leftDateTime, leftEndpointOpen, rightDateTime, rightEndpointOpen, not)); + } + + @Override + public void exitNameBasic(QueryLangParser.NameBasicContext ctx) { + StringPath stringPath = QUser.user.name; + Token op = ctx.stringComparison().op; + boolean not = ctx.stringComparison().NOT() != null; + String string = getStringValue(ctx.stringComparison().STRING().getText()); + + setMongoQuery(ctx, buildStringPredicate(stringPath, op.getType(), string, not)); + } + + @Override + public void exitNameList(QueryLangParser.NameListContext ctx) { + StringPath stringPath = QUser.user.name; + boolean not = ctx.inListStringComparison().NOT() != null; + List stringList = ctx.inListStringComparison().stringList().STRING().stream().map(node -> getStringValue(node.getText())).collect(Collectors.toList()); + + setMongoQuery(ctx, buildStringPredicateInList(stringPath, stringList, not)); + } + + @Override + public void exitNameRange(QueryLangParser.NameRangeContext ctx) { + StringPath stringPath = QUser.user.name; + boolean not = ctx.inRangeStringComparison().NOT() != null; + boolean leftEndpointOpen = ctx.inRangeStringComparison().stringRange().leftEndpoint.getText().equals(LEFT_OPEN_ENDPOINT); + boolean rightEndpointOpen = ctx.inRangeStringComparison().stringRange().rightEndpoint.getText().equals(RIGHT_OPEN_ENDPOINT); + String leftString = getStringValue(ctx.inRangeStringComparison().stringRange().STRING(0).getText()); + String rightString = getStringValue(ctx.inRangeStringComparison().stringRange().STRING(1).getText()); + + setMongoQuery(ctx, buildStringPredicateInRange(stringPath, leftString, leftEndpointOpen, rightString, rightEndpointOpen, not)); + } + + @Override + public void exitSurnameBasic(QueryLangParser.SurnameBasicContext ctx) { + StringPath stringPath = QUser.user.surname; + Token op = ctx.stringComparison().op; + boolean not = ctx.stringComparison().NOT() != null; + String string = getStringValue(ctx.stringComparison().STRING().getText()); + + setMongoQuery(ctx, buildStringPredicate(stringPath, op.getType(), string, not)); + } + + @Override + public void exitSurnameList(QueryLangParser.SurnameListContext ctx) { + StringPath stringPath = QUser.user.surname; + boolean not = ctx.inListStringComparison().NOT() != null; + List stringList = ctx.inListStringComparison().stringList().STRING().stream().map(node -> getStringValue(node.getText())).collect(Collectors.toList()); + + setMongoQuery(ctx, buildStringPredicateInList(stringPath, stringList, not)); + } + + @Override + public void exitSurnameRange(QueryLangParser.SurnameRangeContext ctx) { + StringPath stringPath = QUser.user.surname; + boolean not = ctx.inRangeStringComparison().NOT() != null; + boolean leftEndpointOpen = ctx.inRangeStringComparison().stringRange().leftEndpoint.getText().equals(LEFT_OPEN_ENDPOINT); + boolean rightEndpointOpen = ctx.inRangeStringComparison().stringRange().rightEndpoint.getText().equals(RIGHT_OPEN_ENDPOINT); + String leftString = getStringValue(ctx.inRangeStringComparison().stringRange().STRING(0).getText()); + String rightString = getStringValue(ctx.inRangeStringComparison().stringRange().STRING(1).getText()); + + setMongoQuery(ctx, buildStringPredicateInRange(stringPath, leftString, leftEndpointOpen, rightString, rightEndpointOpen, not)); + } + + @Override + public void exitEmailBasic(QueryLangParser.EmailBasicContext ctx) { + StringPath stringPath = QUser.user.email; + Token op = ctx.stringComparison().op; + boolean not = ctx.stringComparison().NOT() != null; + String string = getStringValue(ctx.stringComparison().STRING().getText()); + + setMongoQuery(ctx, buildStringPredicate(stringPath, op.getType(), string, not)); + } + + @Override + public void exitEmailList(QueryLangParser.EmailListContext ctx) { + StringPath stringPath = QUser.user.email; + boolean not = ctx.inListStringComparison().NOT() != null; + List stringList = ctx.inListStringComparison().stringList().STRING().stream().map(node -> getStringValue(node.getText())).collect(Collectors.toList()); + + setMongoQuery(ctx, buildStringPredicateInList(stringPath, stringList, not)); + } + + @Override + public void exitEmailRange(QueryLangParser.EmailRangeContext ctx) { + StringPath stringPath = QUser.user.email; + boolean not = ctx.inRangeStringComparison().NOT() != null; + boolean leftEndpointOpen = ctx.inRangeStringComparison().stringRange().leftEndpoint.getText().equals(LEFT_OPEN_ENDPOINT); + boolean rightEndpointOpen = ctx.inRangeStringComparison().stringRange().rightEndpoint.getText().equals(RIGHT_OPEN_ENDPOINT); + String leftString = getStringValue(ctx.inRangeStringComparison().stringRange().STRING(0).getText()); + String rightString = getStringValue(ctx.inRangeStringComparison().stringRange().STRING(1).getText()); + + setMongoQuery(ctx, buildStringPredicateInRange(stringPath, leftString, leftEndpointOpen, rightString, rightEndpointOpen, not)); + } + + @Override + public void exitDataString(QueryLangParser.DataStringContext ctx) { + String fieldId = ctx.dataValue().fieldId.getText(); + Token op = ctx.stringComparison().op; + checkOp(ComparisonType.STRING, op); + boolean not = ctx.stringComparison().NOT() != null; + String string = getStringValue(ctx.stringComparison().STRING().getText()); + + setMongoQuery(ctx, null); + setElasticQuery(ctx, buildElasticQuery("dataSet." + fieldId + ".textValue", op.getType(), string, not)); + this.searchWithElastic = true; + } + + @Override + public void exitDataStringList(QueryLangParser.DataStringListContext ctx) { + String fieldId = ctx.dataValue().fieldId.getText(); + boolean not = ctx.inListStringComparison().NOT() != null; + List stringList = ctx.inListStringComparison().stringList().STRING().stream().map(node -> getStringValue(node.getText())).collect(Collectors.toList()); + + setMongoQuery(ctx, null); + setElasticQuery(ctx, buildElasticQueryInList("dataSet." + fieldId + ".textValue", stringList, not)); + this.searchWithElastic = true; + } + + @Override + public void exitDataStringRange(QueryLangParser.DataStringRangeContext ctx) { + String fieldId = ctx.dataValue().fieldId.getText(); + boolean not = ctx.inRangeStringComparison().NOT() != null; + boolean leftEndpointOpen = ctx.inRangeStringComparison().stringRange().leftEndpoint.getText().equals(LEFT_OPEN_ENDPOINT); + boolean rightEndpointOpen = ctx.inRangeStringComparison().stringRange().rightEndpoint.getText().equals(RIGHT_OPEN_ENDPOINT); + String leftString = getStringValue(ctx.inRangeStringComparison().stringRange().STRING(0).getText()); + String rightString = getStringValue(ctx.inRangeStringComparison().stringRange().STRING(1).getText()); + + setMongoQuery(ctx, null); + setElasticQuery(ctx, buildElasticQueryInRange("dataSet." + fieldId + ".textValue", leftString, leftEndpointOpen, rightString, rightEndpointOpen, not)); + this.searchWithElastic = true; + } + + @Override + public void exitDataNumber(QueryLangParser.DataNumberContext ctx) { + String fieldId = ctx.dataValue().fieldId.getText(); + Token op = ctx.numberComparison().op; + checkOp(ComparisonType.NUMBER, op); + boolean not = ctx.numberComparison().NOT() != null; + String number = ctx.numberComparison().number.getText(); + + setMongoQuery(ctx, null); + setElasticQuery(ctx, buildElasticQuery("dataSet." + fieldId + ".numberValue", op.getType(), number, not)); + this.searchWithElastic = true; + } + + @Override + public void exitDataNumberList(QueryLangParser.DataNumberListContext ctx) { + String fieldId = ctx.dataValue().fieldId.getText(); + boolean not = ctx.inListNumberComparison().NOT() != null; + List terminalNodeList = ctx.inListNumberComparison().intList() != null ? ctx.inListNumberComparison().intList().INT() : ctx.inListNumberComparison().doubleList().DOUBLE(); + List stringNumberList = terminalNodeList.stream().map(TerminalNode::getText).collect(Collectors.toList()); + + setMongoQuery(ctx, null); + setElasticQuery(ctx, buildElasticQueryInList("dataSet." + fieldId + ".numberValue", stringNumberList, not)); + this.searchWithElastic = true; + } + + @Override + public void exitDataNumberRange(QueryLangParser.DataNumberRangeContext ctx) { + String fieldId = ctx.dataValue().fieldId.getText(); + boolean not = ctx.inRangeNumberComparison().NOT() != null; + boolean leftEndpointOpen; + boolean rightEndpointOpen; + String leftNumberAsString; + String rightNumberAsString; + if (ctx.inRangeNumberComparison().intRange() != null) { + leftEndpointOpen = ctx.inRangeNumberComparison().intRange().leftEndpoint.getText().equals(LEFT_OPEN_ENDPOINT); + rightEndpointOpen = ctx.inRangeNumberComparison().intRange().rightEndpoint.getText().equals(RIGHT_OPEN_ENDPOINT); + leftNumberAsString = ctx.inRangeNumberComparison().intRange().INT(0).getText(); + rightNumberAsString = ctx.inRangeNumberComparison().intRange().INT(1).getText(); + } else { + leftEndpointOpen = ctx.inRangeNumberComparison().doubleRange().leftEndpoint.getText().equals(LEFT_OPEN_ENDPOINT); + rightEndpointOpen = ctx.inRangeNumberComparison().doubleRange().rightEndpoint.getText().equals(RIGHT_OPEN_ENDPOINT); + leftNumberAsString = ctx.inRangeNumberComparison().doubleRange().DOUBLE(0).getText(); + rightNumberAsString = ctx.inRangeNumberComparison().doubleRange().DOUBLE(1).getText(); + } + + setMongoQuery(ctx, null); + setElasticQuery(ctx, buildElasticQueryInRange("dataSet." + fieldId + ".numberValue", leftNumberAsString, leftEndpointOpen, rightNumberAsString, rightEndpointOpen, not)); + this.searchWithElastic = true; + } + + @Override + public void exitDataDate(QueryLangParser.DataDateContext ctx) { + String fieldId = ctx.dataValue().fieldId.getText(); + Token op = ctx.dateComparison().op; + checkOp(ComparisonType.DATE, op); + boolean not = ctx.dateComparison().NOT() != null; + LocalDateTime localDateTime = toDateTime(ctx.dateComparison().DATE().getText()); + + setMongoQuery(ctx, null); + setElasticQuery(ctx, buildElasticQuery("dataSet." + fieldId + ".timestampValue", op.getType(), String.valueOf(Timestamp.valueOf(localDateTime).getTime()), not)); + this.searchWithElastic = true; + } + + @Override + public void exitDataDatetime(QueryLangParser.DataDatetimeContext ctx) { + String fieldId = ctx.dataValue().fieldId.getText(); + Token op = ctx.dateTimeComparison().op; + checkOp(ComparisonType.DATETIME, op); + boolean not = ctx.dateTimeComparison().NOT() != null; + LocalDateTime localDateTime = toDateTime(ctx.dateTimeComparison().DATETIME().getText()); + + setMongoQuery(ctx, null); + setElasticQuery(ctx, buildElasticQuery("dataSet." + fieldId + ".timestampValue", op.getType(), String.valueOf(Timestamp.valueOf(localDateTime).getTime()), not)); + this.searchWithElastic = true; + } + + @Override + public void exitDataDateList(QueryLangParser.DataDateListContext ctx) { + String fieldId = ctx.dataValue().fieldId.getText(); + boolean not = ctx.inListDateComparison().NOT() != null; + List terminalNodeList = ctx.inListDateComparison().dateList() != null ? ctx.inListDateComparison().dateList().DATE() : ctx.inListDateComparison().dateTimeList().DATETIME(); + List stringNumberList = terminalNodeList.stream().map(TerminalNode::getText).map(dateAsString -> { + LocalDateTime localDateTime = toDateTime(dateAsString); + return String.valueOf(Timestamp.valueOf(localDateTime).getTime()); + }).collect(Collectors.toList()); + + setMongoQuery(ctx, null); + setElasticQuery(ctx, buildElasticQueryInList("dataSet." + fieldId + ".timestampValue", stringNumberList, not)); + this.searchWithElastic = true; + } + + @Override + public void exitDataDateRange(QueryLangParser.DataDateRangeContext ctx) { + String fieldId = ctx.dataValue().fieldId.getText(); + boolean not = ctx.inRangeDateComparison().NOT() != null; + boolean leftEndpointOpen; + boolean rightEndpointOpen; + LocalDateTime leftDateTime; + LocalDateTime rightDateTime; + if (ctx.inRangeDateComparison().dateRange() != null) { + leftEndpointOpen = ctx.inRangeDateComparison().dateRange().leftEndpoint.getText().equals(LEFT_OPEN_ENDPOINT); + rightEndpointOpen = ctx.inRangeDateComparison().dateRange().rightEndpoint.getText().equals(RIGHT_OPEN_ENDPOINT); + leftDateTime = toDateTime(ctx.inRangeDateComparison().dateRange().DATE(0).getText()); + rightDateTime = toDateTime(ctx.inRangeDateComparison().dateRange().DATE(1).getText()); + } else { + leftEndpointOpen = ctx.inRangeDateComparison().dateTimeRange().leftEndpoint.getText().equals(LEFT_OPEN_ENDPOINT); + rightEndpointOpen = ctx.inRangeDateComparison().dateTimeRange().rightEndpoint.getText().equals(RIGHT_OPEN_ENDPOINT); + leftDateTime = toDateTime(ctx.inRangeDateComparison().dateTimeRange().DATETIME(0).getText()); + rightDateTime = toDateTime(ctx.inRangeDateComparison().dateTimeRange().DATETIME(1).getText()); + } + + setMongoQuery(ctx, null); + setElasticQuery(ctx, buildElasticQueryInRange("dataSet." + fieldId + ".timestampValue", String.valueOf(Timestamp.valueOf(leftDateTime).getTime()), leftEndpointOpen, String.valueOf(Timestamp.valueOf(rightDateTime).getTime()), rightEndpointOpen, not)); + this.searchWithElastic = true; + } + + @Override + public void exitDataBoolean(QueryLangParser.DataBooleanContext ctx) { + String fieldId = ctx.dataValue().fieldId.getText(); + Token op = ctx.booleanComparison().op; + checkOp(ComparisonType.BOOLEAN, op); + boolean not = ctx.booleanComparison().NOT() != null; + String booleanValue = ctx.booleanComparison().BOOLEAN().getText(); + + setMongoQuery(ctx, null); + setElasticQuery(ctx, buildElasticQuery("dataSet." + fieldId + ".booleanValue", op.getType(), booleanValue, not)); + this.searchWithElastic = true; + } + + @Override + public void exitDataOptionsBasic(QueryLangParser.DataOptionsBasicContext ctx) { + String fieldId = ctx.dataOptions().fieldId.getText(); + Token op = ctx.stringComparison().op; + checkOp(ComparisonType.STRING, op); + boolean not = ctx.stringComparison().NOT() != null; + String string = getStringValue(ctx.stringComparison().STRING().getText()); + + setMongoQuery(ctx, null); + setElasticQuery(ctx, buildElasticQuery("dataSet." + fieldId + ".options", op.getType(), string, not)); + this.searchWithElastic = true; + } + + @Override + public void exitDataOptionsList(QueryLangParser.DataOptionsListContext ctx) { + String fieldId = ctx.dataOptions().fieldId.getText(); + boolean not = ctx.inListStringComparison().NOT() != null; + List stringList = ctx.inListStringComparison().stringList().STRING().stream().map(node -> getStringValue(node.getText())).collect(Collectors.toList()); + + setMongoQuery(ctx, null); + setElasticQuery(ctx, buildElasticQueryInList("dataSet." + fieldId + ".options", stringList, not)); + this.searchWithElastic = true; + } + + @Override + public void exitDataOptionsRange(QueryLangParser.DataOptionsRangeContext ctx) { + String fieldId = ctx.dataOptions().fieldId.getText(); + boolean not = ctx.inRangeStringComparison().NOT() != null; + boolean leftEndpointOpen = ctx.inRangeStringComparison().stringRange().leftEndpoint.getText().equals(LEFT_OPEN_ENDPOINT); + boolean rightEndpointOpen = ctx.inRangeStringComparison().stringRange().rightEndpoint.getText().equals(RIGHT_OPEN_ENDPOINT); + String leftString = getStringValue(ctx.inRangeStringComparison().stringRange().STRING(0).getText()); + String rightString = getStringValue(ctx.inRangeStringComparison().stringRange().STRING(1).getText()); + + setMongoQuery(ctx, null); + setElasticQuery(ctx, buildElasticQueryInRange("dataSet." + fieldId + ".options", leftString, leftEndpointOpen, rightString, rightEndpointOpen, not)); + this.searchWithElastic = true; + } + + @Override + public void exitPlacesBasic(QueryLangParser.PlacesBasicContext ctx) { + String placeId = ctx.places().placeId.getText(); + Token op = ctx.numberComparison().op; + checkOp(ComparisonType.NUMBER, op); + boolean not = ctx.numberComparison().NOT() != null; + String numberValue = ctx.numberComparison().number.getText(); + + setMongoQuery(ctx, null); + setElasticQuery(ctx, buildElasticQuery("places." + placeId + ".marking", op.getType(), numberValue, not)); + this.searchWithElastic = true; + } + + @Override + public void exitPlacesList(QueryLangParser.PlacesListContext ctx) { + String placeId = ctx.places().placeId.getText(); + boolean not = ctx.inListNumberComparison().NOT() != null; + List terminalNodeList = ctx.inListNumberComparison().intList() != null ? ctx.inListNumberComparison().intList().INT() : ctx.inListNumberComparison().doubleList().DOUBLE(); + List stringNumberList = terminalNodeList.stream().map(TerminalNode::getText).collect(Collectors.toList()); + + setMongoQuery(ctx, null); + setElasticQuery(ctx, buildElasticQueryInList("places." + placeId + ".marking", stringNumberList, not)); + this.searchWithElastic = true; + } + + @Override + public void exitPlacesRange(QueryLangParser.PlacesRangeContext ctx) { + String placeId = ctx.places().placeId.getText(); + boolean not = ctx.inRangeNumberComparison().NOT() != null; + boolean leftEndpointOpen; + boolean rightEndpointOpen; + String leftNumberAsString; + String rightNumberAsString; + if (ctx.inRangeNumberComparison().intRange() != null) { + leftEndpointOpen = ctx.inRangeNumberComparison().intRange().leftEndpoint.getText().equals(LEFT_OPEN_ENDPOINT); + rightEndpointOpen = ctx.inRangeNumberComparison().intRange().rightEndpoint.getText().equals(RIGHT_OPEN_ENDPOINT); + leftNumberAsString = ctx.inRangeNumberComparison().intRange().INT(0).getText(); + rightNumberAsString = ctx.inRangeNumberComparison().intRange().INT(1).getText(); + } else { + leftEndpointOpen = ctx.inRangeNumberComparison().doubleRange().leftEndpoint.getText().equals(LEFT_OPEN_ENDPOINT); + rightEndpointOpen = ctx.inRangeNumberComparison().doubleRange().rightEndpoint.getText().equals(RIGHT_OPEN_ENDPOINT); + leftNumberAsString = ctx.inRangeNumberComparison().doubleRange().DOUBLE(0).getText(); + rightNumberAsString = ctx.inRangeNumberComparison().doubleRange().DOUBLE(1).getText(); + } + + setMongoQuery(ctx, null); + setElasticQuery(ctx, buildElasticQueryInRange("places." + placeId + ".marking", leftNumberAsString, leftEndpointOpen, rightNumberAsString, rightEndpointOpen, not)); + this.searchWithElastic = true; + } + + @Override + public void exitTasksStateComparison(QueryLangParser.TasksStateComparisonContext ctx) { + String taskId = ctx.tasksState().taskId.getText(); + Token op = ctx.op; + checkOp(ComparisonType.STRING, op); + boolean not = ctx.NOT() != null; + State state = ctx.state.getType() == QueryLangParser.ENABLED ? State.ENABLED : State.DISABLED; + + setMongoQuery(ctx, null); + setElasticQuery(ctx, buildElasticQuery("tasks." + taskId + ".state", op.getType(), state.toString(), not)); + this.searchWithElastic = true; + } + + @Override + public void exitTasksUserIdBasic(QueryLangParser.TasksUserIdBasicContext ctx) { + String taskId = ctx.tasksUserId().taskId.getText(); + Token op = ctx.stringComparison().op; + checkOp(ComparisonType.STRING, op); + boolean not = ctx.stringComparison().NOT() != null; + String string = getStringValue(ctx.stringComparison().STRING().getText()); + + setMongoQuery(ctx, null); + setElasticQuery(ctx, buildElasticQuery("tasks." + taskId + ".userId", op.getType(), string, not)); + this.searchWithElastic = true; + } + + @Override + public void exitTasksUserIdList(QueryLangParser.TasksUserIdListContext ctx) { + String taskId = ctx.tasksUserId().taskId.getText(); + boolean not = ctx.inListStringComparison().NOT() != null; + List stringList = ctx.inListStringComparison().stringList().STRING().stream() + .map(node -> getStringValue(node.getText())) + .collect(Collectors.toList()); + + setMongoQuery(ctx, null); + setElasticQuery(ctx, buildElasticQueryInList("tasks." + taskId + ".userId", stringList, not)); + this.searchWithElastic = true; + } + + @Override + public void exitPaging(QueryLangParser.PagingContext ctx) { + pageNumber = Integer.parseInt(ctx.pageNum.getText()); + + if (ctx.pageSize != null) { + pageSize = Integer.parseInt(ctx.pageSize.getText()); + } + } + + @Override + public void exitCaseSorting(QueryLangParser.CaseSortingContext ctx) { + ctx.caseAttributeOrdering().forEach(attrOrd -> { + Sort.Direction dir = attrOrd.ordering != null ? Sort.Direction.fromString(attrOrd.ordering.getText()) : Sort.Direction.ASC; + String prop; + if (searchWithElastic) { + // todo NAE-1997: sorting by data value, options + if (attrOrd.caseAttribute().places() != null) { + prop = "places." + attrOrd.caseAttribute().places().placeId.getText() + ".marking"; + } else if (attrOrd.caseAttribute().tasksState() != null) { + prop = "tasks." + attrOrd.caseAttribute().tasksState().taskId.getText() + ".state.keyword"; + } else if (attrOrd.caseAttribute().tasksUserId() != null) { + prop = "tasks." + attrOrd.caseAttribute().tasksUserId().taskId.getText() + ".userId.keyword"; + } else { + prop = caseAttrToSortPropElasticMapping.get(attrOrd.caseAttribute().getText().toLowerCase()); + } + } else { + prop = caseAttrToSortPropMapping.get(attrOrd.caseAttribute().getText().toLowerCase()); + } + + if (prop == null) { + return; + } + sortOrders.add(new Sort.Order(dir, prop)); + }); + } + + @Override + public void exitProcessSorting(QueryLangParser.ProcessSortingContext ctx) { + ctx.processAttributeOrdering().forEach(attrOrd -> { + Sort.Direction dir = attrOrd.ordering != null ? Sort.Direction.fromString(attrOrd.ordering.getText()) : Sort.Direction.ASC; + String prop = processAttrToSortPropMapping.get(attrOrd.processAttribute().getText().toLowerCase()); + if (prop == null) { + return; + } + sortOrders.add(new Sort.Order(dir, prop)); + }); + } + + @Override + public void exitTaskSorting(QueryLangParser.TaskSortingContext ctx) { + ctx.taskAttributeOrdering().forEach(attrOrd -> { + Sort.Direction dir = attrOrd.ordering != null ? Sort.Direction.fromString(attrOrd.ordering.getText()) : Sort.Direction.ASC; + String prop = taskAttrToSortPropMapping.get(attrOrd.taskAttribute().getText().toLowerCase()); + if (prop == null) { + return; + } + sortOrders.add(new Sort.Order(dir, prop)); + }); + } + + @Override + public void exitUserSorting(QueryLangParser.UserSortingContext ctx) { + ctx.userAttributeOrdering().forEach(attrOrd -> { + Sort.Direction dir = attrOrd.ordering != null ? Sort.Direction.fromString(attrOrd.ordering.getText()) : Sort.Direction.ASC; + String prop = userAttrToSortPropMapping.get(attrOrd.userAttribute().getText().toLowerCase()); + if (prop == null) { + return; + } + sortOrders.add(new Sort.Order(dir, prop)); + }); + } +} diff --git a/src/main/java/com/netgrif/application/engine/search/QueryLangExplainEvaluator.java b/src/main/java/com/netgrif/application/engine/search/QueryLangExplainEvaluator.java new file mode 100644 index 00000000000..c02f6cc586b --- /dev/null +++ b/src/main/java/com/netgrif/application/engine/search/QueryLangExplainEvaluator.java @@ -0,0 +1,505 @@ +package com.netgrif.application.engine.search; + +import com.netgrif.application.engine.search.antlr4.QueryLangBaseListener; +import com.netgrif.application.engine.search.antlr4.QueryLangParser; +import com.netgrif.application.engine.search.enums.QueryType; +import com.netgrif.application.engine.search.utils.QueryLangTreeNode; +import lombok.Getter; +import org.antlr.v4.runtime.tree.ErrorNodeImpl; +import org.antlr.v4.runtime.tree.ParseTree; +import org.antlr.v4.runtime.tree.ParseTreeProperty; +import org.springframework.data.domain.Sort; + +import java.util.ArrayList; +import java.util.List; +import java.util.Objects; +import java.util.stream.Collectors; +import java.util.stream.Stream; + +import static com.netgrif.application.engine.search.utils.SearchUtils.*; + +public class QueryLangExplainEvaluator extends QueryLangBaseListener { + + private final ParseTreeProperty node = new ParseTreeProperty<>(); + @Getter + private QueryLangTreeNode root = null; + + @Getter + private QueryType type; + @Getter + private boolean multiple; + @Getter + private boolean searchWithElastic = false; + + private int pageNumber = 0; + private int pageSize = 20; + private final List sortOrders = new ArrayList<>(); + + public String explain() { + StringBuilder result = new StringBuilder("Searching "); + + result.append(multiple ? "multiple instances" : "single instance") + .append(" of resource ") + .append(type != null ? type.name() : "invalid type") + .append(" with ") + .append(searchWithElastic ? "Elasticsearch" : "MongoDB") + .append(".\n") + .append("page number: ").append(pageNumber) + .append(", page size: ").append(pageSize) + .append("\n"); + + if (!sortOrders.isEmpty()) { + result.append("sort by ").append(String.join(";", sortOrders)).append("\n"); + } + + result.append(root != null ? root.toString() : "Tree visualisation not available."); + + return result.toString(); + } + + public void setQueryLangTreeNode(ParseTree node, QueryLangTreeNode queryLangTreeNode) { + if (queryLangTreeNode == null) { + queryLangTreeNode = new QueryLangTreeNode("error: " + node.getText()); + } + this.node.put(node, queryLangTreeNode); + } + + public QueryLangTreeNode getQueryLangTreeNode(ParseTree node) { + return this.node.get(node); + } + + private static QueryLangTreeNode createTreeNode(String name, List children, List errors) { + List combinedChildren = Stream.concat(children.stream(), errors.stream()).collect(Collectors.toList()); + return new QueryLangTreeNode(name, combinedChildren); + } + + private QueryLangTreeNode getErrorFromNode(ParseTree node) { + if (node instanceof ErrorNodeImpl) { + String errorMsg = "error: " + ((ErrorNodeImpl) node).symbol.getText(); + return new QueryLangTreeNode(errorMsg); + } + return null; + } + + private List getErrorsFromChildren(List children) { + List errors = new ArrayList<>(); + children.forEach(child -> { + if (child instanceof ErrorNodeImpl) { + errors.add(getErrorFromNode(child)); + } + }); + return errors; + } + + private List getErrorsRecursive(ParseTree node) { + List errors = new ArrayList<>(); + if (node.getChildCount() == 0) { + if (node instanceof ErrorNodeImpl) { + errors.add(getErrorFromNode(node)); + } + return errors; + } + + int numChildren = node.getChildCount(); + for (int i = 0; i < numChildren; i++) { + errors.addAll(getErrorsRecursive(node.getChild(i))); + } + return errors; + } + + private void processComplexExpression(String nodeName, List children, ParseTree current) { + if (children.size() == 1) { + setQueryLangTreeNode(current, getQueryLangTreeNode(children.get(0))); + return; + } + List errorNodes = getErrorsFromChildren(children); + + List childrenNodes = children.stream() + .map(child -> { + QueryLangTreeNode node = getQueryLangTreeNode(child); + if (node == null) { + errorNodes.addAll(getErrorsRecursive(child)); + } + return node; + }) + .filter(Objects::nonNull) + .collect(Collectors.toList()); + + + setQueryLangTreeNode(current, createTreeNode(nodeName, childrenNodes, errorNodes)); + } + + @Override + public void enterProcessQuery(QueryLangParser.ProcessQueryContext ctx) { + type = QueryType.PROCESS; + multiple = ctx.resource.getType() == QueryLangParser.PROCESSES; + } + + @Override + public void exitProcessQuery(QueryLangParser.ProcessQueryContext ctx) { + root = createTreeNode("process query", List.of(getQueryLangTreeNode(ctx.processConditions())), getErrorsFromChildren(ctx.children)); + } + + @Override + public void enterCaseQuery(QueryLangParser.CaseQueryContext ctx) { + type = QueryType.CASE; + multiple = ctx.resource.getType() == QueryLangParser.CASES; + } + + @Override + public void exitCaseQuery(QueryLangParser.CaseQueryContext ctx) { + QueryLangTreeNode childTreeNode = getQueryLangTreeNode(ctx.caseConditions()); + root = createTreeNode("case query", List.of(childTreeNode), getErrorsFromChildren(ctx.children)); + } + + @Override + public void enterTaskQuery(QueryLangParser.TaskQueryContext ctx) { + type = QueryType.TASK; + multiple = ctx.resource.getType() == QueryLangParser.TASKS; + } + + @Override + public void exitTaskQuery(QueryLangParser.TaskQueryContext ctx) { + root = createTreeNode("task query", List.of(getQueryLangTreeNode(ctx.taskConditions())), getErrorsFromChildren(ctx.children)); + } + + @Override + public void enterUserQuery(QueryLangParser.UserQueryContext ctx) { + type = QueryType.USER; + multiple = ctx.resource.getType() == QueryLangParser.USERS; + } + + @Override + public void exitUserQuery(QueryLangParser.UserQueryContext ctx) { + root = createTreeNode("user query", List.of(getQueryLangTreeNode(ctx.userConditions())), getErrorsFromChildren(ctx.children)); + } + + @Override + public void exitProcessConditions(QueryLangParser.ProcessConditionsContext ctx) { + setQueryLangTreeNode(ctx, getQueryLangTreeNode(ctx.processOrExpression())); + } + + @Override + public void exitProcessOrExpression(QueryLangParser.ProcessOrExpressionContext ctx) { + List children = ctx.processAndExpression().stream() + .map(andExpression -> (ParseTree) andExpression) + .collect(Collectors.toList()); + + processComplexExpression("OR", children, ctx); + } + + @Override + public void exitProcessAndExpression(QueryLangParser.ProcessAndExpressionContext ctx) { + List children = ctx.processConditionGroup().stream() + .map(conditionGroup -> (ParseTree) conditionGroup) + .collect(Collectors.toList()); + + processComplexExpression("AND", children, ctx); + } + + @Override + public void exitProcessConditionGroupBasic(QueryLangParser.ProcessConditionGroupBasicContext ctx) { + setQueryLangTreeNode(ctx, getQueryLangTreeNode(ctx.processCondition())); + } + + @Override + public void exitProcessConditionGroupParenthesis(QueryLangParser.ProcessConditionGroupParenthesisContext ctx) { + setQueryLangTreeNode(ctx, createTreeNode("()", List.of(getQueryLangTreeNode(ctx.processConditions())), getErrorsFromChildren(ctx.children))); + } + + @Override + public void exitCaseConditions(QueryLangParser.CaseConditionsContext ctx) { + setQueryLangTreeNode(ctx, getQueryLangTreeNode(ctx.caseOrExpression())); + } + + @Override + public void exitCaseOrExpression(QueryLangParser.CaseOrExpressionContext ctx) { + List children = ctx.caseAndExpression().stream() + .map(andExpression -> (ParseTree) andExpression) + .collect(Collectors.toList()); + + processComplexExpression("OR", children, ctx); + } + + @Override + public void exitCaseAndExpression(QueryLangParser.CaseAndExpressionContext ctx) { + List children = ctx.caseConditionGroup().stream() + .map(conditionGroup -> (ParseTree) conditionGroup) + .collect(Collectors.toList()); + + processComplexExpression("AND", children, ctx); + } + + @Override + public void exitCaseConditionGroupBasic(QueryLangParser.CaseConditionGroupBasicContext ctx) { + setQueryLangTreeNode(ctx, getQueryLangTreeNode(ctx.caseCondition())); + } + + + @Override + public void exitCaseConditionGroupParenthesis(QueryLangParser.CaseConditionGroupParenthesisContext ctx) { + setQueryLangTreeNode(ctx, createTreeNode("()", List.of(getQueryLangTreeNode(ctx.caseConditions())), getErrorsFromChildren(ctx.children))); + } + + @Override + public void exitTaskConditions(QueryLangParser.TaskConditionsContext ctx) { + setQueryLangTreeNode(ctx, getQueryLangTreeNode(ctx.taskOrExpression())); + } + + @Override + public void exitTaskOrExpression(QueryLangParser.TaskOrExpressionContext ctx) { + List children = ctx.taskAndExpression().stream() + .map(andExpression -> (ParseTree) andExpression) + .collect(Collectors.toList()); + + processComplexExpression("OR", children, ctx); + } + + @Override + public void exitTaskAndExpression(QueryLangParser.TaskAndExpressionContext ctx) { + List children = ctx.taskConditionGroup().stream() + .map(conditionGroup -> (ParseTree) conditionGroup) + .collect(Collectors.toList()); + + processComplexExpression("AND", children, ctx); + } + + @Override + public void exitTaskConditionGroupBasic(QueryLangParser.TaskConditionGroupBasicContext ctx) { + setQueryLangTreeNode(ctx, getQueryLangTreeNode(ctx.taskCondition())); + } + + @Override + public void exitTaskConditionGroupParenthesis(QueryLangParser.TaskConditionGroupParenthesisContext ctx) { + setQueryLangTreeNode(ctx, createTreeNode("()", List.of(getQueryLangTreeNode(ctx.taskConditions())), getErrorsFromChildren(ctx.children))); + } + + @Override + public void exitUserConditions(QueryLangParser.UserConditionsContext ctx) { + setQueryLangTreeNode(ctx, getQueryLangTreeNode(ctx.userOrExpression())); + } + + @Override + public void exitUserOrExpression(QueryLangParser.UserOrExpressionContext ctx) { + List children = ctx.userAndExpression().stream() + .map(andExpression -> (ParseTree) andExpression) + .collect(Collectors.toList()); + + processComplexExpression("OR", children, ctx); + } + + @Override + public void exitUserAndExpression(QueryLangParser.UserAndExpressionContext ctx) { + List children = ctx.userConditionGroup().stream() + .map(conditionGroup -> (ParseTree) conditionGroup) + .collect(Collectors.toList()); + + processComplexExpression("AND", children, ctx); + } + + @Override + public void exitUserConditionGroupBasic(QueryLangParser.UserConditionGroupBasicContext ctx) { + setQueryLangTreeNode(ctx, getQueryLangTreeNode(ctx.userCondition())); + } + + @Override + public void exitUserConditionGroupParenthesis(QueryLangParser.UserConditionGroupParenthesisContext ctx) { + setQueryLangTreeNode(ctx, createTreeNode("()", List.of(getQueryLangTreeNode(ctx.userConditions())), getErrorsFromChildren(ctx.children))); + } + + @Override + public void exitProcessCondition(QueryLangParser.ProcessConditionContext ctx) { + List errors = getErrorsRecursive(ctx); + setQueryLangTreeNode(ctx, new QueryLangTreeNode(ctx.getText(), errors)); + } + + @Override + public void exitCaseCondition(QueryLangParser.CaseConditionContext ctx) { + List errors = getErrorsRecursive(ctx); + setQueryLangTreeNode(ctx, new QueryLangTreeNode(ctx.getText(), errors)); + } + + @Override + public void exitTaskCondition(QueryLangParser.TaskConditionContext ctx) { + List errors = getErrorsRecursive(ctx); + setQueryLangTreeNode(ctx, new QueryLangTreeNode(ctx.getText(), errors)); + } + + @Override + public void exitUserCondition(QueryLangParser.UserConditionContext ctx) { + List errors = getErrorsRecursive(ctx); + setQueryLangTreeNode(ctx, new QueryLangTreeNode(ctx.getText(), errors)); + } + + @Override + public void exitPlacesBasic(QueryLangParser.PlacesBasicContext ctx) { + searchWithElastic = true; + } + + @Override + public void enterPlacesList(QueryLangParser.PlacesListContext ctx) { + searchWithElastic = true; + } + + @Override + public void enterPlacesRange(QueryLangParser.PlacesRangeContext ctx) { + searchWithElastic = true; + } + + @Override + public void exitTasksStateComparison(QueryLangParser.TasksStateComparisonContext ctx) { + searchWithElastic = true; + } + + @Override + public void exitTasksUserIdBasic(QueryLangParser.TasksUserIdBasicContext ctx) { + searchWithElastic = true; + } + + @Override + public void exitTasksUserIdList(QueryLangParser.TasksUserIdListContext ctx) { + searchWithElastic = true; + } + + @Override + public void exitDataString(QueryLangParser.DataStringContext ctx) { + this.searchWithElastic = true; + } + + @Override + public void exitDataNumber(QueryLangParser.DataNumberContext ctx) { + this.searchWithElastic = true; + } + + @Override + public void exitDataDate(QueryLangParser.DataDateContext ctx) { + this.searchWithElastic = true; + } + + @Override + public void exitDataDatetime(QueryLangParser.DataDatetimeContext ctx) { + this.searchWithElastic = true; + } + + @Override + public void exitDataBoolean(QueryLangParser.DataBooleanContext ctx) { + this.searchWithElastic = true; + } + + @Override + public void exitDataDateList(QueryLangParser.DataDateListContext ctx) { + searchWithElastic = true; + } + + @Override + public void exitDataDateRange(QueryLangParser.DataDateRangeContext ctx) { + searchWithElastic = true; + } + + @Override + public void exitDataNumberList(QueryLangParser.DataNumberListContext ctx) { + searchWithElastic = true; + } + + @Override + public void exitDataNumberRange(QueryLangParser.DataNumberRangeContext ctx) { + searchWithElastic = true; + } + + @Override + public void exitDataStringList(QueryLangParser.DataStringListContext ctx) { + searchWithElastic = true; + } + + @Override + public void exitDataStringRange(QueryLangParser.DataStringRangeContext ctx) { + searchWithElastic = true; + } + + @Override + public void exitDataOptionsBasic(QueryLangParser.DataOptionsBasicContext ctx) { + this.searchWithElastic = true; + } + + @Override + public void exitDataOptionsList(QueryLangParser.DataOptionsListContext ctx) { + searchWithElastic = true; + } + + @Override + public void exitDataOptionsRange(QueryLangParser.DataOptionsRangeContext ctx) { + searchWithElastic = true; + } + + @Override + public void exitPaging(QueryLangParser.PagingContext ctx) { + pageNumber = Integer.parseInt(ctx.pageNum.getText()); + + if (ctx.pageSize != null) { + pageSize = Integer.parseInt(ctx.pageSize.getText()); + } + } + + @Override + public void exitCaseSorting(QueryLangParser.CaseSortingContext ctx) { + ctx.caseAttributeOrdering().forEach(attrOrd -> { + Sort.Direction dir = attrOrd.ordering != null ? Sort.Direction.fromString(attrOrd.ordering.getText()) : Sort.Direction.ASC; + String prop; + if (searchWithElastic) { + // todo NAE-1997: sorting by data value, options + if (attrOrd.caseAttribute().places() != null) { + prop = "places." + attrOrd.caseAttribute().places().placeId.getText() + ".marking"; + } else if (attrOrd.caseAttribute().tasksState() != null) { + prop = "tasks." + attrOrd.caseAttribute().tasksState().taskId.getText() + ".state.keyword"; + } else if (attrOrd.caseAttribute().tasksUserId() != null) { + prop = "tasks." + attrOrd.caseAttribute().tasksUserId().taskId.getText() + ".userId.keyword"; + } else { + prop = caseAttrToSortPropElasticMapping.get(attrOrd.caseAttribute().getText().toLowerCase()); + } + } else { + prop = caseAttrToSortPropMapping.get(attrOrd.caseAttribute().getText().toLowerCase()); + } + + if (prop == null) { + sortOrders.add("Invalid attribute: " + attrOrd.caseAttribute().getText()); + } + sortOrders.add("attribute: " + prop + ", ordering: " + dir); + }); + } + + @Override + public void exitProcessSorting(QueryLangParser.ProcessSortingContext ctx) { + ctx.processAttributeOrdering().forEach(attrOrd -> { + Sort.Direction dir = attrOrd.ordering != null ? Sort.Direction.fromString(attrOrd.ordering.getText()) : Sort.Direction.ASC; + String prop = processAttrToSortPropMapping.get(attrOrd.processAttribute().getText().toLowerCase()); + if (prop == null) { + sortOrders.add("Invalid attribute: " + attrOrd.processAttribute().getText()); + } + sortOrders.add("attribute: " + prop + ", ordering: " + dir); + }); + } + + @Override + public void exitTaskSorting(QueryLangParser.TaskSortingContext ctx) { + ctx.taskAttributeOrdering().forEach(attrOrd -> { + Sort.Direction dir = attrOrd.ordering != null ? Sort.Direction.fromString(attrOrd.ordering.getText()) : Sort.Direction.ASC; + String prop = taskAttrToSortPropMapping.get(attrOrd.taskAttribute().getText().toLowerCase()); + if (prop == null) { + sortOrders.add("Invalid attribute: " + attrOrd.taskAttribute().getText()); + } + sortOrders.add("attribute: " + prop + ", ordering: " + dir); + }); + } + + @Override + public void exitUserSorting(QueryLangParser.UserSortingContext ctx) { + ctx.userAttributeOrdering().forEach(attrOrd -> { + Sort.Direction dir = attrOrd.ordering != null ? Sort.Direction.fromString(attrOrd.ordering.getText()) : Sort.Direction.ASC; + String prop = userAttrToSortPropMapping.get(attrOrd.userAttribute().getText().toLowerCase()); + if (prop == null) { + sortOrders.add("Invalid attribute: " + attrOrd.userAttribute().getText()); + } + sortOrders.add("attribute: " + prop + ", ordering: " + dir); + }); + } +} diff --git a/src/main/java/com/netgrif/application/engine/search/SearchService.java b/src/main/java/com/netgrif/application/engine/search/SearchService.java new file mode 100644 index 00000000000..f78ee099b06 --- /dev/null +++ b/src/main/java/com/netgrif/application/engine/search/SearchService.java @@ -0,0 +1,158 @@ +package com.netgrif.application.engine.search; + +import com.netgrif.application.engine.auth.domain.repositories.UserRepository; +import com.netgrif.application.engine.auth.service.interfaces.IUserService; +import com.netgrif.application.engine.elastic.service.interfaces.IElasticCaseService; +import com.netgrif.application.engine.elastic.web.requestbodies.CaseSearchRequest; +import com.netgrif.application.engine.petrinet.domain.repositories.PetriNetRepository; +import com.netgrif.application.engine.search.interfaces.ISearchService; +import com.netgrif.application.engine.search.utils.SearchUtils; +import com.netgrif.application.engine.workflow.domain.Case; +import com.netgrif.application.engine.workflow.domain.repositories.CaseRepository; +import com.netgrif.application.engine.workflow.domain.repositories.TaskRepository; +import com.querydsl.core.types.Predicate; +import lombok.RequiredArgsConstructor; +import lombok.extern.slf4j.Slf4j; +import org.springframework.context.i18n.LocaleContextHolder; +import org.springframework.data.domain.PageRequest; +import org.springframework.data.domain.Pageable; +import org.springframework.stereotype.Service; + +import java.util.List; + +import static com.netgrif.application.engine.search.utils.SearchUtils.evaluateQuery; + +@Slf4j +@Service +@RequiredArgsConstructor +public class SearchService implements ISearchService { + + private final PetriNetRepository petriNetRepository; + + private final IElasticCaseService elasticCaseService; + + private final CaseRepository caseRepository; + + private final TaskRepository taskRepository; + + private final UserRepository userRepository; + + private final IUserService userService; + + private Long countCasesElastic(String elasticQuery) { + CaseSearchRequest caseSearchRequest = new CaseSearchRequest(); + caseSearchRequest.query = elasticQuery; + return elasticCaseService.count( + List.of(caseSearchRequest), + userService.getLoggedOrSystem().transformToLoggedUser(), + LocaleContextHolder.getLocale(), + false + ); + } + + private List findCasesElastic(String elasticQuery, Pageable pageable) { + CaseSearchRequest caseSearchRequest = new CaseSearchRequest(); + caseSearchRequest.query = elasticQuery; + return elasticCaseService.search( + List.of(caseSearchRequest), + userService.getLoggedOrSystem().transformToLoggedUser(), + pageable, + LocaleContextHolder.getLocale(), + false + ).getContent(); + } + + private boolean existsCasesElastic(String elasticQuery) { + return countCasesElastic(elasticQuery) > 0; + } + + @Override + public String explainQuery(String input) { + return SearchUtils.explainQuery(input); + } + + @Override + public Object search(String input) { + QueryLangEvaluator evaluator = evaluateQuery(input); + Predicate predicate = evaluator.getFullMongoQuery(); + String elasticQuery = evaluator.getFullElasticQuery(); + Pageable pageable = evaluator.getPageable(); + + switch (evaluator.getType()) { + case PROCESS: + if (evaluator.getMultiple()) { + return petriNetRepository.findAll(predicate, pageable).getContent(); + } + return petriNetRepository.findAll(predicate, PageRequest.of(0, 1)) + .getContent().stream().findFirst().orElse(null); + case CASE: + if (!evaluator.getSearchWithElastic()) { + if (evaluator.getMultiple()) { + return caseRepository.findAll(predicate, pageable).getContent(); + } + return caseRepository.findAll(predicate, PageRequest.of(0, 1)) + .getContent().stream().findFirst().orElse(null); + } + + List cases = findCasesElastic(elasticQuery, pageable); + return evaluator.getMultiple() ? cases : cases.stream().findFirst().orElse(null); + case TASK: + if (evaluator.getMultiple()) { + return taskRepository.findAll(predicate, pageable).getContent(); + } + return taskRepository.findAll(predicate, PageRequest.of(0, 1)) + .getContent().stream().findFirst().orElse(null); + case USER: + if (evaluator.getMultiple()) { + return userRepository.findAll(predicate, pageable).getContent(); + } + return userRepository.findAll(predicate, PageRequest.of(0, 1)) + .getContent().stream().findFirst().orElse(null); + } + return null; + } + + @Override + public Long count(String input) { + QueryLangEvaluator evaluator = evaluateQuery(input); + Predicate predicate = evaluator.getFullMongoQuery(); + String elasticQuery = evaluator.getFullElasticQuery(); + + switch (evaluator.getType()) { + case PROCESS: + return petriNetRepository.count(predicate); + case CASE: + if (!evaluator.getSearchWithElastic()) { + return caseRepository.count(predicate); + } + return countCasesElastic(elasticQuery); + case TASK: + return taskRepository.count(predicate); + case USER: + return userRepository.count(predicate); + } + return null; + } + + @Override + public boolean exists(String input) { + QueryLangEvaluator evaluator = evaluateQuery(input); + Predicate predicate = evaluator.getFullMongoQuery(); + String elasticQuery = evaluator.getFullElasticQuery(); + + switch (evaluator.getType()) { + case PROCESS: + return petriNetRepository.exists(predicate); + case CASE: + if (!evaluator.getSearchWithElastic()) { + return caseRepository.exists(predicate); + } + return existsCasesElastic(elasticQuery); + case TASK: + return taskRepository.exists(predicate); + case USER: + return userRepository.exists(predicate); + } + return false; + } +} diff --git a/src/main/java/com/netgrif/application/engine/search/antlr4/QueryLang.g4 b/src/main/java/com/netgrif/application/engine/search/antlr4/QueryLang.g4 new file mode 100644 index 00000000000..88c455c6c03 --- /dev/null +++ b/src/main/java/com/netgrif/application/engine/search/antlr4/QueryLang.g4 @@ -0,0 +1,360 @@ +// todo NAE-1997: generate this with plugin +grammar QueryLang; + +query: resource=(PROCESS | PROCESSES) delimeter processConditions (paging)? (processSorting)? EOF # processQuery + | resource=(CASE | CASES) delimeter caseConditions (paging)? (caseSorting)? EOF # caseQuery + | resource=(TASK | TASKS) delimeter taskConditions (paging)? (taskSorting)? EOF # taskQuery + | resource=(USER | USERS) delimeter userConditions (paging)? (userSorting)? EOF # userQuery + ; + +processConditions: processOrExpression ; +processOrExpression: processAndExpression (SPACE OR SPACE processAndExpression)* ; +processAndExpression: processConditionGroup (SPACE AND SPACE processConditionGroup)* ; +processConditionGroup: processCondition # processConditionGroupBasic + | (NOT SPACE?)? '(' SPACE? processConditions SPACE? ')' SPACE? # processConditionGroupParenthesis + ; +processCondition: processComparisons SPACE? ; + +caseConditions: caseOrExpression ; +caseOrExpression: caseAndExpression (SPACE OR SPACE caseAndExpression)* ; +caseAndExpression: caseConditionGroup (SPACE AND SPACE caseConditionGroup)* ; +caseConditionGroup: caseCondition # caseConditionGroupBasic + | (NOT SPACE?)? '(' SPACE? caseConditions SPACE? ')' SPACE? # caseConditionGroupParenthesis + ; +caseCondition: caseComparisons SPACE? ; + +taskConditions: taskOrExpression ; +taskOrExpression: taskAndExpression (SPACE OR SPACE taskAndExpression)* ; +taskAndExpression: taskConditionGroup (SPACE AND SPACE taskConditionGroup)* ; +taskConditionGroup: taskCondition # taskConditionGroupBasic + | (NOT SPACE?)? '(' SPACE? taskConditions SPACE? ')' SPACE? # taskConditionGroupParenthesis + ; +taskCondition: taskComparisons SPACE? ; + +userConditions: userOrExpression ; +userOrExpression: userAndExpression (SPACE OR SPACE userAndExpression)* ; +userAndExpression: userConditionGroup (SPACE AND SPACE userConditionGroup)* ; +userConditionGroup: userCondition # userConditionGroupBasic + | (NOT SPACE?)? '(' SPACE? userConditions SPACE? ')' SPACE? # userConditionGroupParenthesis + ; +userCondition: userComparisons SPACE? ; + +// delimeter +delimeter: SPACE WHERE SPACE | SPACE? ':' SPACE ; +WHERE: W H E R E ; + +// paging +paging: PAGE SPACE pageNum=INT (SPACE SIZE SPACE pageSize=INT)? SPACE?; + +// sorting +processSorting: SORT_BY SPACE processAttributeOrdering (',' SPACE? processAttributeOrdering)* SPACE?; +processAttributeOrdering: processAttribute (SPACE ordering=(ASC | DESC))? ; +processAttribute: ID + | IDENTIFIER + | VERSION + | TITLE + | CREATION_DATE + ; + +caseSorting: SORT_BY SPACE caseAttributeOrdering (',' SPACE? caseAttributeOrdering)* SPACE?; +caseAttributeOrdering: caseAttribute (SPACE ordering=(ASC | DESC))? ; +caseAttribute: ID + | PROCESS_ID + | PROCESS_IDENTIFIER + | TITLE + | CREATION_DATE + | AUTHOR + | places + | tasksUserId + | tasksState + | dataValue + | dataOptions + ; + +taskSorting: SORT_BY SPACE taskAttributeOrdering (',' SPACE? taskAttributeOrdering)* SPACE?; +taskAttributeOrdering: taskAttribute (SPACE ordering=(ASC | DESC))? ; +taskAttribute: ID + | TRANSITION_ID + | TITLE + | STATE + | USER_ID + | CASE_ID + | PROCESS_ID + | LAST_ASSIGN + | LAST_FINISH + ; + +userSorting: SORT_BY SPACE userAttributeOrdering (',' SPACE? userAttributeOrdering)* SPACE?; +userAttributeOrdering: userAttribute (SPACE ordering=(ASC | DESC))? ; +userAttribute: ID + | NAME + | SURNAME + | EMAIL + ; + +// resource comparisons +processComparisons: idComparison + | identifierComparison + | versionComparison + | titleComparison + | creationDateComparison + ; + +caseComparisons: idComparison + | processIdObjIdComparison + | processIdentifierComparison + | titleComparison + | creationDateComparison + | authorComparison + | placesComparison + | tasksStateComparison + | tasksUserIdComparison + | dataValueComparison + | dataOptionsComparison + ; + +taskComparisons: idComparison + | transitionIdComparison + | titleComparison + | stateComparison + | userIdComparison + | caseIdComparison + | processIdComparison + | lastAssignComparison + | lastFinishComparison + ; + +userComparisons: idComparison + | nameComparison + | surnameComparison + | emailComparison + ; + +// attribute comparisons +idComparison: ID SPACE objectIdComparison # idBasic + | ID SPACE inListStringComparison # idList + ; +titleComparison: TITLE SPACE stringComparison # titleBasic + | TITLE SPACE inListStringComparison # titleList + | TITLE SPACE inRangeStringComparison # titleRange + ; +identifierComparison: IDENTIFIER SPACE stringComparison # identifierBasic + | IDENTIFIER SPACE inListStringComparison # identifierList + | IDENTIFIER SPACE inRangeStringComparison # identifierRange + ; +versionComparison: VERSION SPACE (NOT SPACE?)? op=(EQ | LT | GT | LTE | GTE) SPACE VERSION_NUMBER # versionBasic + | VERSION SPACE inListVersionComparison # versionListCmp + | VERSION SPACE inRangeVersionComparison # versionRangeCmp + ; +creationDateComparison: CREATION_DATE SPACE dateComparison # cdDateBasic + | CREATION_DATE SPACE dateTimeComparison # cdDateTimeBasic + | CREATION_DATE SPACE inListDateComparison # cdDateList + | CREATION_DATE SPACE inRangeDateComparison # cdDateRange + ; +processIdComparison: PROCESS_ID SPACE stringComparison # processIdBasic + | PROCESS_ID SPACE inListStringComparison # processIdList + ; +processIdObjIdComparison: PROCESS_ID SPACE objectIdComparison # processIdObjIdBasic + | PROCESS_ID SPACE inListStringComparison # processIdObjIdList + ; +processIdentifierComparison: PROCESS_IDENTIFIER SPACE stringComparison # processIdentifierBasic + | PROCESS_IDENTIFIER SPACE inListStringComparison # processIdentifierList + | PROCESS_IDENTIFIER SPACE inRangeStringComparison # processIdentifierRange + ; +authorComparison: AUTHOR SPACE stringComparison # authorBasic + | AUTHOR SPACE inListStringComparison # authorList + ; +transitionIdComparison: TRANSITION_ID SPACE stringComparison # transitionIdBasic + | TRANSITION_ID SPACE inListStringComparison # transitionIdList + | TRANSITION_ID SPACE inRangeStringComparison # transitionIdRange + ; +stateComparison: STATE SPACE EQ SPACE state=(ENABLED | DISABLED) ; +userIdComparison: USER_ID SPACE stringComparison # userIdBasic + | USER_ID SPACE inListStringComparison # userIdList + ; +caseIdComparison: CASE_ID SPACE stringComparison # caseIdBasic + | CASE_ID SPACE inListStringComparison # caseIdList + ; +lastAssignComparison: LAST_ASSIGN SPACE dateComparison # laDateBasic + | LAST_ASSIGN SPACE dateTimeComparison # laDateTimeBasic + | LAST_ASSIGN SPACE inListDateComparison # laDateList + | LAST_ASSIGN SPACE inRangeDateComparison # laDateRange + ; +lastFinishComparison: LAST_FINISH SPACE dateComparison # lfDateBasic + | LAST_FINISH SPACE dateTimeComparison # lfDateTimeBasic + | LAST_FINISH SPACE inListDateComparison # lfDateList + | LAST_FINISH SPACE inRangeDateComparison # lfDateRange + ; +nameComparison: NAME SPACE stringComparison # nameBasic + | NAME SPACE inListStringComparison # nameList + | NAME SPACE inRangeStringComparison # nameRange + ; +surnameComparison: SURNAME SPACE stringComparison # surnameBasic + | SURNAME SPACE inListStringComparison # surnameList + | SURNAME SPACE inRangeStringComparison # surnameRange + ; +emailComparison: EMAIL SPACE stringComparison # emailBasic + | EMAIL SPACE inListStringComparison # emailList + | EMAIL SPACE inRangeStringComparison # emailRange + ; +dataValueComparison: dataValue SPACE stringComparison # dataString + | dataValue SPACE numberComparison # dataNumber + | dataValue SPACE dateComparison # dataDate + | dataValue SPACE dateTimeComparison # dataDatetime + | dataValue SPACE booleanComparison # dataBoolean + | dataValue SPACE inListStringComparison # dataStringList + | dataValue SPACE inListNumberComparison # dataNumberList + | dataValue SPACE inListDateComparison # dataDateList + | dataValue SPACE inRangeStringComparison # dataStringRange + | dataValue SPACE inRangeNumberComparison # dataNumberRange + | dataValue SPACE inRangeDateComparison # dataDateRange + ; +dataOptionsComparison: dataOptions SPACE stringComparison # dataOptionsBasic + | dataOptions SPACE inListStringComparison # dataOptionsList + | dataOptions SPACE inRangeStringComparison # dataOptionsRange + ; +placesComparison: places SPACE numberComparison # placesBasic + | places SPACE inListNumberComparison # placesList + | places SPACE inRangeNumberComparison # placesRange + ; +tasksStateComparison: tasksState SPACE (NOT SPACE?)? op=EQ SPACE state=(ENABLED | DISABLED) ; +tasksUserIdComparison: tasksUserId SPACE stringComparison # tasksUserIdBasic + | tasksUserId SPACE inListStringComparison # tasksUserIdList + ; + +// basic comparisons +objectIdComparison: (NOT SPACE?)? op=EQ SPACE STRING ; +stringComparison: (NOT SPACE?)? op=(EQ | CONTAINS | LT | GT | LTE | GTE) SPACE STRING ; +numberComparison: (NOT SPACE?)? op=(EQ | LT | GT | LTE | GTE) SPACE number=(INT | DOUBLE) ; +dateComparison: (NOT SPACE?)? op=(EQ | LT | GT | LTE | GTE) SPACE DATE ; +dateTimeComparison: (NOT SPACE?)? op=(EQ | LT | GT | LTE | GTE) SPACE DATETIME ; +booleanComparison: (NOT SPACE?)? op=EQ SPACE BOOLEAN ; + +// in list/in range comparisons +inListStringComparison: (NOT SPACE?)? op=IN SPACE stringList ; +inListNumberComparison: (NOT SPACE?)? op=IN SPACE (intList | doubleList) ; +inListDateComparison: (NOT SPACE?)? op=IN SPACE (dateList | dateTimeList) ; +inListVersionComparison: (NOT SPACE?)? op=IN SPACE versionList ; +inRangeStringComparison: (NOT SPACE?)? op=IN SPACE stringRange ; +inRangeNumberComparison: (NOT SPACE?)? op=IN SPACE (intRange | doubleRange) ; +inRangeDateComparison: (NOT SPACE?)? op=IN SPACE (dateRange | dateTimeRange) ; +inRangeVersionComparison: (NOT SPACE?)? op=IN SPACE versionRange ; + +// special attribute rules +dataValue: DATA '.' fieldId=JAVA_ID '.' VALUE ; +dataOptions: DATA '.' fieldId=JAVA_ID '.' OPTIONS ; +places: PLACES '.' placeId=JAVA_ID '.' MARKING ; +tasksState: TASKS '.' taskId=JAVA_ID '.' STATE ; +tasksUserId: TASKS '.' taskId=JAVA_ID '.' USER_ID ; + +// operators +AND: A N D | '&' ; +OR: O R | '|' ; +NOT: N O T | '!' ; +EQ: E Q | '==' ; +LT: L T | '<' ; +GT: G T | '>' ; +LTE: L T E | '<=' ; +GTE: G T E | '>=' ; +CONTAINS: C O N T A I N S | '~'; +IN: I N ; + +// resurces +CASE: C A S E ; +CASES: C A S E S ; +TASK: T A S K ; +TASKS: T A S K S ; +USER: U S E R ; +USERS: U S E R S ; +PROCESS: P R O C E S S ; +PROCESSES: P R O C E S S E S ; + +// attributes +ID: I D ; +TITLE: T I T L E ; +IDENTIFIER: I D E N T I F I E R ; +VERSION: V E R S I O N ; +CREATION_DATE: C R E A T I O N D A T E ; +PROCESS_ID: P R O C E S S I D ; +PROCESS_IDENTIFIER: P R O C E S S I D E N T I F I E R ; +AUTHOR: A U T H O R ; +PLACES: P L A C E S ; +TRANSITION_ID: T R A N S I T I O N I D ; +STATE: S T A T E ; +USER_ID: U S E R I D ; +CASE_ID: C A S E I D ; +LAST_ASSIGN: L A S T A S S I G N ; +LAST_FINISH: L A S T F I N I S H ; +NAME: N A M E ; +SURNAME: S U R N A M E ; +EMAIL: E M A I L ; + +DATA: D A T A ; +VALUE: V A L U E ; +OPTIONS: O P T I O N S ; +MARKING: M A R K I N G ; +ENABLED: E N A B L E D ; +DISABLED: D I S A B L E D ; + +// paging +PAGE: P A G E ; +SIZE: S I Z E ; + +// sorting +SORT_BY: S O R T SPACE B Y ; +ASC: A S C ; +DESC: D E S C ; + +// basic types +stringList: '(' SPACE? (STRING SPACE? (',' SPACE? STRING SPACE? )* )? SPACE? ')' ; +intList: '(' SPACE? (INT SPACE? (',' SPACE? INT SPACE? )* )? SPACE? ')' ; +doubleList: '(' SPACE? (DOUBLE SPACE? (',' SPACE? DOUBLE SPACE? )* )? SPACE? ')' ; +dateList: '(' SPACE? (DATE SPACE? (',' SPACE? DATE SPACE? )* )? SPACE? ')' ; +dateTimeList: '(' SPACE? (DATETIME SPACE? (',' SPACE? DATETIME SPACE? )* )? SPACE? ')' ; +versionList: '(' SPACE? (VERSION_NUMBER SPACE? (',' SPACE? VERSION_NUMBER SPACE? )* )? SPACE? ')' ; +stringRange: leftEndpoint=('(' | '[') SPACE? STRING SPACE? ':' SPACE? STRING SPACE? rightEndpoint=(')' | ']') ; +intRange: leftEndpoint=('(' | '[') SPACE? INT SPACE? ':' SPACE? INT SPACE? rightEndpoint=(')' | ']') ; +doubleRange: leftEndpoint=('(' | '[') SPACE? DOUBLE SPACE? ':' SPACE? DOUBLE SPACE? rightEndpoint=(')' | ']') ; +dateRange: leftEndpoint=('(' | '[') SPACE? DATE SPACE? ':' SPACE? DATE SPACE? rightEndpoint=(')' | ']') ; +dateTimeRange: leftEndpoint=('(' | '[') SPACE? DATETIME SPACE? ':' SPACE? DATETIME SPACE? rightEndpoint=(')' | ']') ; +versionRange: leftEndpoint=('(' | '[') SPACE? VERSION_NUMBER SPACE? ':' SPACE? VERSION_NUMBER SPACE? rightEndpoint=(')' | ']') ; +STRING: '\'' (~('\'' | '\r' | '\n'))* '\'' ; // todo NAE-1997: escape??? +INT: DIGIT+ ; +DOUBLE: DIGIT+ '.' DIGIT+ ; +DATETIME: DATE 'T' ([01] DIGIT | '2' [0-3]) ':' [0-5] DIGIT ':' [0-5] DIGIT ('.' DIGIT+)? ; // 2020-03-03T20:00:00.055 // todo NAE-1997: format +DATE: DIGIT DIGIT DIGIT DIGIT '-' ('0' [1-9] | '1' [0-2]) '-' ('0' [1-9] | [12] DIGIT | '3' [01]) ; // 2020-03-03 // todo NAE-1997: format +BOOLEAN: T R U E | F A L S E ; +VERSION_NUMBER: DIGIT+ '.' DIGIT+ '.' DIGIT+ ; +JAVA_ID: [a-zA-Z$_] [a-zA-Z0-9$_]* ; + +SPACE: [ ]+ ; +ANY: . ; + +// fragments +fragment A : [aA]; +fragment B : [bB]; +fragment C : [cC]; +fragment D : [dD]; +fragment E : [eE]; +fragment F : [fF]; +fragment G : [gG]; +fragment H : [hH]; +fragment I : [iI]; +fragment J : [jJ]; +fragment K : [kK]; +fragment L : [lL]; +fragment M : [mM]; +fragment N : [nN]; +fragment O : [oO]; +fragment P : [pP]; +fragment Q : [qQ]; +fragment R : [rR]; +fragment S : [sS]; +fragment T : [tT]; +fragment U : [uU]; +fragment V : [vV]; +fragment W : [wW]; +fragment X : [xX]; +fragment Y : [yY]; +fragment Z : [zZ]; +fragment DIGIT: [0-9]; diff --git a/src/main/java/com/netgrif/application/engine/search/enums/ComparisonType.java b/src/main/java/com/netgrif/application/engine/search/enums/ComparisonType.java new file mode 100644 index 00000000000..a013863f7e5 --- /dev/null +++ b/src/main/java/com/netgrif/application/engine/search/enums/ComparisonType.java @@ -0,0 +1,11 @@ +package com.netgrif.application.engine.search.enums; + +public enum ComparisonType { + ID, + STRING, + NUMBER, + DATE, + DATETIME, + BOOLEAN, + OPTIONS +} diff --git a/src/main/java/com/netgrif/application/engine/search/enums/QueryType.java b/src/main/java/com/netgrif/application/engine/search/enums/QueryType.java new file mode 100644 index 00000000000..10453af537f --- /dev/null +++ b/src/main/java/com/netgrif/application/engine/search/enums/QueryType.java @@ -0,0 +1,8 @@ +package com.netgrif.application.engine.search.enums; + +public enum QueryType { + PROCESS, + CASE, + TASK, + USER +} diff --git a/src/main/java/com/netgrif/application/engine/search/interfaces/ISearchService.java b/src/main/java/com/netgrif/application/engine/search/interfaces/ISearchService.java new file mode 100644 index 00000000000..b8dddee63bc --- /dev/null +++ b/src/main/java/com/netgrif/application/engine/search/interfaces/ISearchService.java @@ -0,0 +1,11 @@ +package com.netgrif.application.engine.search.interfaces; + +public interface ISearchService { + String explainQuery(String query); + + Object search(String query); + + Long count(String query); + + boolean exists(String query); +} diff --git a/src/main/java/com/netgrif/application/engine/search/utils/QueryLangTreeNode.java b/src/main/java/com/netgrif/application/engine/search/utils/QueryLangTreeNode.java new file mode 100644 index 00000000000..440952b211c --- /dev/null +++ b/src/main/java/com/netgrif/application/engine/search/utils/QueryLangTreeNode.java @@ -0,0 +1,43 @@ +package com.netgrif.application.engine.search.utils; + +import java.util.ArrayList; +import java.util.Iterator; +import java.util.List; + +/** + * https://stackoverflow.com/a/8948691 + */ +public class QueryLangTreeNode { + final String name; + final List children; + + public QueryLangTreeNode(String name, List children) { + this.name = name; + this.children = children; + } + + public QueryLangTreeNode(String name) { + this.name = name; + this.children = new ArrayList<>(); + } + + public String toString() { + StringBuilder buffer = new StringBuilder(50); + print(buffer, "", ""); + return buffer.toString(); + } + + private void print(StringBuilder buffer, String prefix, String childrenPrefix) { + buffer.append(prefix); + buffer.append(name); + buffer.append('\n'); + for (Iterator it = children.iterator(); it.hasNext();) { + QueryLangTreeNode next = it.next(); + if (it.hasNext()) { + next.print(buffer, childrenPrefix + "├── ", childrenPrefix + "│ "); + } else { + next.print(buffer, childrenPrefix + "└── ", childrenPrefix + " "); + } + } + } +} diff --git a/src/main/java/com/netgrif/application/engine/search/utils/SearchUtils.java b/src/main/java/com/netgrif/application/engine/search/utils/SearchUtils.java new file mode 100644 index 00000000000..2d88ad0ef87 --- /dev/null +++ b/src/main/java/com/netgrif/application/engine/search/utils/SearchUtils.java @@ -0,0 +1,425 @@ +package com.netgrif.application.engine.search.utils; + +import com.netgrif.application.engine.search.QueryLangErrorListener; +import com.netgrif.application.engine.search.QueryLangEvaluator; +import com.netgrif.application.engine.search.QueryLangExplainEvaluator; +import com.netgrif.application.engine.search.antlr4.QueryLangBaseListener; +import com.netgrif.application.engine.search.antlr4.QueryLangLexer; +import com.netgrif.application.engine.search.antlr4.QueryLangParser; +import com.netgrif.application.engine.petrinet.domain.QPetriNet; +import com.netgrif.application.engine.petrinet.domain.version.QVersion; +import com.netgrif.application.engine.petrinet.domain.version.Version; +import com.netgrif.application.engine.search.enums.ComparisonType; +import com.querydsl.core.BooleanBuilder; +import com.querydsl.core.types.Predicate; +import com.querydsl.core.types.dsl.BooleanExpression; +import com.querydsl.core.types.dsl.DateTimePath; +import com.querydsl.core.types.dsl.StringPath; +import lombok.extern.slf4j.Slf4j; +import org.antlr.v4.runtime.CharStreams; +import org.antlr.v4.runtime.CommonTokenStream; +import org.antlr.v4.runtime.Token; +import org.antlr.v4.runtime.tree.ParseTreeWalker; +import org.bson.types.ObjectId; +import org.bson.types.QObjectId; + +import java.time.LocalDate; +import java.time.LocalDateTime; +import java.time.format.DateTimeFormatter; +import java.time.format.DateTimeParseException; +import java.util.List; +import java.util.Map; +import java.util.stream.Collectors; + +@Slf4j +public class SearchUtils { + + public static final Map> comparisonOperators = Map.of( + ComparisonType.ID, List.of(QueryLangParser.EQ, QueryLangParser.IN), + ComparisonType.STRING, List.of(QueryLangParser.EQ, QueryLangParser.CONTAINS, QueryLangParser.LT, QueryLangParser.LTE, QueryLangParser.GT, QueryLangParser.GTE), + ComparisonType.NUMBER, List.of(QueryLangParser.EQ, QueryLangParser.LT, QueryLangParser.LTE, QueryLangParser.GT, QueryLangParser.GTE), + ComparisonType.DATE, List.of(QueryLangParser.EQ, QueryLangParser.LT, QueryLangParser.LTE, QueryLangParser.GT, QueryLangParser.GTE), + ComparisonType.DATETIME, List.of(QueryLangParser.EQ, QueryLangParser.LT, QueryLangParser.LTE, QueryLangParser.GT, QueryLangParser.GTE), + ComparisonType.BOOLEAN, List.of(QueryLangParser.EQ) + ); + + public static final Map processAttrToSortPropMapping = Map.of( + "id", "id", + "identifier", "identifier", + "version", "version", + "title", "title.defaultValue", + "creationdate", "creationDate" + ); + + public static final Map caseAttrToSortPropMapping = Map.of( + "id", "id", + "processidentifier", "processIdentifier", + "processid", "petriNetObjectId", + "title", "title", + "creationdate", "creationDate", + "author", "author.id" + ); + + public static final Map caseAttrToSortPropElasticMapping = Map.of( + "id", "stringId.keyword", + "processidentifier", "processIdentifier.keyword", + "processid", "processId.keyword", + "title", "title.keyword", + "creationdate", "creationDateSortable", + "author", "author.keyword" + ); + + public static final Map taskAttrToSortPropMapping = Map.of( + "id", "id", + "transitionid", "transitionId", + "title", "title.defaultValue", + "state", "state", + "userid", "userId", + "caseid", "caseId", + "processid", "processId", + "lastassign", "lastAssigned", + "lastfinish", "lastFinished" + ); + + public static final Map userAttrToSortPropMapping = Map.of( + "id", "id", + "name", "name", + "surname", "surname", + "email", "email" + ); + + public static final String LEFT_OPEN_ENDPOINT = "("; + public static final String RIGHT_OPEN_ENDPOINT = ")"; + + public static String toDateString(LocalDate localDate) { + return localDate.format(DateTimeFormatter.ISO_LOCAL_DATE); + } + + public static String toDateString(LocalDateTime localDateTime) { + return localDateTime.format(DateTimeFormatter.ISO_LOCAL_DATE); + } + + public static String toDateTimeString(LocalDate localDate) { + return localDate.atTime(12, 0, 0).format(DateTimeFormatter.ISO_LOCAL_DATE_TIME); + } + + public static String toDateTimeString(LocalDateTime localDateTime) { + return localDateTime.format(DateTimeFormatter.ISO_LOCAL_DATE_TIME); + } + + public static LocalDateTime toDateTime(String dateTimeString) { + try { + return LocalDateTime.parse(dateTimeString, DateTimeFormatter.ISO_LOCAL_DATE_TIME); + } catch (DateTimeParseException ignored) { + try { + return LocalDate.parse(dateTimeString, DateTimeFormatter.ISO_LOCAL_DATE).atTime(12, 0, 0); + } catch (DateTimeParseException e) { + throw new IllegalArgumentException("Invalid date/datetime format"); + } + } + } + + public static LocalDate toDate(String dateString) { + try { + return LocalDate.parse(dateString, DateTimeFormatter.ISO_LOCAL_DATE); + } catch (DateTimeParseException ignored) { + try { + return LocalDateTime.parse(dateString, DateTimeFormatter.ISO_LOCAL_DATE_TIME).toLocalDate(); + } catch (DateTimeParseException e) { + throw new IllegalArgumentException("Invalid date/datetime format"); + } + } + } + + public static QueryLangExplainEvaluator explainQueryInternal(ParseTreeWalker walker, QueryLangParser.QueryContext query, QueryLangErrorListener errorListener) { + QueryLangExplainEvaluator evaluator = new QueryLangExplainEvaluator(); + walker.walk(evaluator, query); + + if (!errorListener.getErrorMessages().isEmpty()) { + throw new IllegalArgumentException("\n" + evaluator.explain() + "\n" + String.join("\n", errorListener.getErrorMessages())); + } + + return evaluator; + } + + private static QueryLangBaseListener evaluateQueryInternal(String input, boolean onlyExplain) { + if (input == null || input.isEmpty()) { + throw new IllegalArgumentException("Query cannot be empty."); + } + + QueryLangLexer lexer = new QueryLangLexer(CharStreams.fromString(input)); + CommonTokenStream tokens = new CommonTokenStream(lexer); + QueryLangParser parser = new QueryLangParser(tokens); + QueryLangErrorListener errorListener = new QueryLangErrorListener(); + parser.removeErrorListeners(); + parser.addErrorListener(errorListener); + + QueryLangParser.QueryContext query = parser.query(); + ParseTreeWalker walker = new ParseTreeWalker(); + + if (onlyExplain || !errorListener.getErrorMessages().isEmpty()) { + return explainQueryInternal(walker, query, errorListener); + } + + QueryLangEvaluator evaluator = new QueryLangEvaluator(); + walker.walk(evaluator, query); + + return evaluator; + } + + public static QueryLangEvaluator evaluateQuery(String input) { + return (QueryLangEvaluator) evaluateQueryInternal(input, false); + } + + public static String explainQuery(String input) { + QueryLangExplainEvaluator evaluator = (QueryLangExplainEvaluator) evaluateQueryInternal(input, true); + return evaluator.explain(); + } + + public static String getStringValue(String queryLangString) { + return queryLangString.replace("'", ""); + } + + public static ObjectId getObjectIdValue(String queryLangString) { + String objectId = getStringValue(queryLangString); + if (ObjectId.isValid(objectId)) { + return new ObjectId(objectId); + } + + throw new IllegalArgumentException("Invalid objectId: " + objectId); + } + + public static void checkOp(ComparisonType type, Token op) { + if (!comparisonOperators.get(type).contains(op.getType())) { + throw new IllegalArgumentException("Operator " + op.getText() + " is not applicable for type " + type.toString()); + } + } + + public static Predicate buildObjectIdPredicate(QObjectId qObjectId, int op, ObjectId objectId, boolean not) { + if (op != QueryLangParser.EQ) { + throw new UnsupportedOperationException("Operator is not available for id comparison"); + } + + Predicate predicate = qObjectId.eq(objectId); + if (not) { + predicate = predicate.not(); + } + return predicate; + } + + public static Predicate buildObjectIdPredicateInList(QObjectId qObjectId, List values, boolean not) { + Predicate predicate = qObjectId.in(values); + + return not ? predicate.not() : predicate; + } + + public static Predicate buildStringPredicate(StringPath stringPath, int op, String string, boolean not) { + Predicate predicate = null; + switch (op) { + case QueryLangParser.EQ: + predicate = stringPath.eq(string); + break; + case QueryLangParser.CONTAINS: + predicate = stringPath.contains(string); + break; + case QueryLangParser.LT: + predicate = stringPath.lt(string); + break; + case QueryLangParser.LTE: + predicate = stringPath.loe(string); + break; + case QueryLangParser.GT: + predicate = stringPath.gt(string); + break; + case QueryLangParser.GTE: + predicate = stringPath.goe(string); + break; + } + + if (predicate == null) { + throw new UnsupportedOperationException("Operator is not available for string comparison"); + } + + if (not) { + predicate = predicate.not(); + } + return predicate; + } + + public static Predicate buildStringPredicateInList(StringPath stringPath, List values, boolean not) { + Predicate predicate = stringPath.in(values); + + return not ? predicate.not() : predicate; + } + + public static Predicate buildStringPredicateInRange(StringPath stringPath, String leftValue, boolean leftEndpointOpen, String rightValue, boolean rightEndpointOpen, boolean not) { + BooleanExpression leftExpression = leftEndpointOpen ? stringPath.gt(leftValue) : stringPath.goe(leftValue); + BooleanExpression rightExpression = rightEndpointOpen ? stringPath.lt(rightValue) : stringPath.loe(rightValue); + Predicate predicate = leftExpression.and(rightExpression); + + return not ? predicate.not() : predicate; + } + + public static Predicate buildVersionPredicate(int op, String versionString, boolean not) { + String[] versionNumber = versionString.split("\\."); + long major = Long.parseLong(versionNumber[0]); + long minor = Long.parseLong(versionNumber[1]); + long patch = Long.parseLong(versionNumber[2]); + + QVersion qVersion = QPetriNet.petriNet.version; + + Predicate predicate = null; + switch (op) { + case QueryLangParser.EQ: + predicate = qVersion.eq(new Version(major, minor, patch)); + break; + case QueryLangParser.GT: + predicate = qVersion.major.gt(major) + .or(qVersion.major.eq(major).and(qVersion.minor.gt(minor))) + .or(qVersion.major.eq(major).and(qVersion.minor.eq(minor).and(qVersion.patch.gt(patch)))); + break; + case QueryLangParser.GTE: + predicate = qVersion.major.goe(major) + .or(qVersion.major.eq(major).and(qVersion.minor.goe(minor))) + .or(qVersion.major.eq(major).and(qVersion.minor.eq(minor).and(qVersion.patch.goe(patch)))); + break; + case QueryLangParser.LT: + predicate = qVersion.major.lt(major) + .or(qVersion.major.eq(major).and(qVersion.minor.lt(minor))) + .or(qVersion.major.eq(major).and(qVersion.minor.eq(minor).and(qVersion.patch.lt(patch)))); + break; + case QueryLangParser.LTE: + predicate = qVersion.major.loe(major) + .or(qVersion.major.eq(major).and(qVersion.minor.loe(minor))) + .or(qVersion.major.eq(major).and(qVersion.minor.eq(minor).and(qVersion.patch.loe(patch)))); + break; + } + + if (predicate == null) { + throw new UnsupportedOperationException("Operator is not available for version comparison"); + } + + if (not) { + predicate = predicate.not(); + } + return predicate; + } + + public static Predicate buildVersionPredicateInList(List values, boolean not) { + List versions = values.stream().map(stringVersion -> { + String[] versionNumber = stringVersion.split("\\."); + long major = Long.parseLong(versionNumber[0]); + long minor = Long.parseLong(versionNumber[1]); + long patch = Long.parseLong(versionNumber[2]); + + return new Version(major, minor, patch); + }).collect(Collectors.toList()); + + Predicate predicate = QPetriNet.petriNet.version.in(versions); + return not ? predicate.not() : predicate; + } + + public static Predicate buildVersionPredicateInRange(String leftValue, boolean leftEndpointOpen, String rightValue, boolean rightEndpointOpen, boolean not) { + Predicate leftExpression = buildVersionPredicate(leftEndpointOpen ? QueryLangParser.GT : QueryLangParser.GTE, leftValue, false); + Predicate rightExpression = buildVersionPredicate(rightEndpointOpen ? QueryLangParser.LT : QueryLangParser.LTE, rightValue, false); + + BooleanBuilder predicate = new BooleanBuilder(); + predicate.and(leftExpression); + predicate.and(rightExpression); + + return not ? predicate.not() : predicate; + } + + public static Predicate buildDateTimePredicate(DateTimePath dateTimePath, int op, LocalDateTime localDateTime, boolean not) { + Predicate predicate = null; + switch (op) { + case QueryLangParser.EQ: + predicate = dateTimePath.eq(localDateTime); + break; + case QueryLangParser.LT: + predicate = dateTimePath.lt(localDateTime); + break; + case QueryLangParser.LTE: + predicate = dateTimePath.loe(localDateTime); + break; + case QueryLangParser.GT: + predicate = dateTimePath.gt(localDateTime); + break; + case QueryLangParser.GTE: + predicate = dateTimePath.goe(localDateTime); + break; + } + + if (predicate == null) { + throw new UnsupportedOperationException("Operator is not available for date/datetime comparison"); + } + + if (not) { + predicate = predicate.not(); + } + return predicate; + } + + public static Predicate buildDateTimePredicateInList(DateTimePath dateTimePath, List values, boolean not) { + List dateTimes = values.stream().map(SearchUtils::toDateTime).collect(Collectors.toList()); + Predicate predicate = dateTimePath.in(dateTimes); + + return not ? predicate.not() : predicate; + } + + public static Predicate buildDateTimePredicateInRange(DateTimePath dateTimePath, LocalDateTime leftValue, boolean leftEndpointOpen, LocalDateTime rightValue, boolean rightEndpointOpen, boolean not) { + BooleanExpression leftExpression = leftEndpointOpen ? dateTimePath.gt(leftValue) : dateTimePath.goe(leftValue); + BooleanExpression rightExpression = rightEndpointOpen ? dateTimePath.lt(rightValue) : dateTimePath.loe(rightValue); + Predicate predicate = leftExpression.and(rightExpression); + + return not ? predicate.not() : predicate; + } + + public static String buildElasticQuery(String attribute, int op, String value, boolean not) { + String query = null; + switch (op) { + case QueryLangParser.EQ: + case QueryLangParser.IN: + query = attribute + ":" + value; + break; + case QueryLangParser.LT: + query = attribute + ":<" + value; + break; + case QueryLangParser.LTE: + query = attribute + ":<=" + value; + break; + case QueryLangParser.GT: + query = attribute + ":>" + value; + break; + case QueryLangParser.GTE: + query = attribute + ":>=" + value; + break; + case QueryLangParser.CONTAINS: + query = attribute + ":*" + value + "*"; + break; + } + + if (query == null) { + throw new UnsupportedOperationException("Operator is not available for elastic comparison"); + } + + if (not) { + query = "NOT " + query; + } + return query; + } + + public static String buildElasticQueryInList(String attribute, List values, boolean not) { + String valuesQuery = "(" + String.join(" OR ", values) + ")"; + return buildElasticQuery(attribute, QueryLangParser.IN, valuesQuery, not); + } + + public static String buildElasticQueryInRange(String attribute, String leftValue, boolean leftEndpointOpen, String rightValue, boolean rightEndpointOpen, boolean not) { + String query = "(" + + buildElasticQuery(attribute, leftEndpointOpen ? QueryLangParser.GT : QueryLangParser.GTE, leftValue, false) + + " AND " + + buildElasticQuery(attribute, rightEndpointOpen ? QueryLangParser.LT : QueryLangParser.LTE, rightValue, false) + + ")"; + return not ? "NOT " + query : query; + } +} diff --git a/src/test/java/com/netgrif/application/engine/search/QueryLangTest.java b/src/test/java/com/netgrif/application/engine/search/QueryLangTest.java new file mode 100644 index 00000000000..57d85b6ec73 --- /dev/null +++ b/src/test/java/com/netgrif/application/engine/search/QueryLangTest.java @@ -0,0 +1,1866 @@ +package com.netgrif.application.engine.search; + +import com.netgrif.application.engine.auth.domain.QUser; +import com.netgrif.application.engine.auth.domain.User; +import com.netgrif.application.engine.petrinet.domain.PetriNet; +import com.netgrif.application.engine.petrinet.domain.QPetriNet; +import com.netgrif.application.engine.petrinet.domain.version.Version; +import com.netgrif.application.engine.search.utils.MongoDbUtils; +import com.netgrif.application.engine.workflow.domain.*; +import com.querydsl.core.BooleanBuilder; +import com.querydsl.core.types.Predicate; +import com.querydsl.core.types.dsl.DateTimePath; +import com.querydsl.core.types.dsl.StringPath; +import lombok.extern.slf4j.Slf4j; +import org.bson.Document; +import org.bson.types.ObjectId; +import org.junit.jupiter.api.Assertions; +import org.junit.jupiter.api.Test; +import org.junit.jupiter.api.extension.ExtendWith; +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.boot.test.context.SpringBootTest; +import org.springframework.data.domain.Pageable; +import org.springframework.data.domain.Sort; +import org.springframework.data.mongodb.core.MongoOperations; +import org.springframework.test.context.ActiveProfiles; +import org.springframework.test.context.junit.jupiter.SpringExtension; + +import java.sql.Timestamp; +import java.time.LocalDateTime; +import java.util.List; + +import static com.netgrif.application.engine.search.utils.SearchUtils.evaluateQuery; + +@Slf4j +@SpringBootTest +@ActiveProfiles({"test"}) +@ExtendWith(SpringExtension.class) +public class QueryLangTest { + public static final ObjectId GENERIC_OBJECT_ID = ObjectId.get(); + + @Autowired + MongoOperations mongoOperations; + + @Test + public void testSimpleMongodbProcessQuery() { + MongoDbUtils mongoDbUtils = new MongoDbUtils<>(mongoOperations, PetriNet.class); + + // id comparison + Predicate actual = evaluateQuery(String.format("process: id eq '%s'", GENERIC_OBJECT_ID)).getFullMongoQuery(); + Predicate expected = QPetriNet.petriNet.id.eq(GENERIC_OBJECT_ID); + + compareMongoQueries(mongoDbUtils, actual, expected); + + actual = evaluateQuery(String.format("process: id in ('%s', '%s')", GENERIC_OBJECT_ID, GENERIC_OBJECT_ID)).getFullMongoQuery(); + expected = QPetriNet.petriNet.id.in(GENERIC_OBJECT_ID, GENERIC_OBJECT_ID); + + compareMongoQueries(mongoDbUtils, actual, expected); + + // identifier comparison + checkStringComparison(mongoDbUtils, "process", "identifier", QPetriNet.petriNet.identifier); + + // version comparison + actual = evaluateQuery("process: version eq 1.1.1").getFullMongoQuery(); + expected = QPetriNet.petriNet.version.eq(new Version(1, 1, 1)); + + compareMongoQueries(mongoDbUtils, actual, expected); + + actual = evaluateQuery("process: version lt 1.1.1").getFullMongoQuery(); + expected = QPetriNet.petriNet.version.major.lt(1) + .or(QPetriNet.petriNet.version.major.eq(1L).and(QPetriNet.petriNet.version.minor.lt(1))) + .or(QPetriNet.petriNet.version.major.eq(1L).and(QPetriNet.petriNet.version.minor.eq(1L).and(QPetriNet.petriNet.version.patch.lt(1)))); + + compareMongoQueries(mongoDbUtils, actual, expected); + + actual = evaluateQuery("process: version lte 1.1.1").getFullMongoQuery(); + expected = QPetriNet.petriNet.version.major.loe(1) + .or(QPetriNet.petriNet.version.major.eq(1L).and(QPetriNet.petriNet.version.minor.loe(1))) + .or(QPetriNet.petriNet.version.major.eq(1L).and(QPetriNet.petriNet.version.minor.eq(1L).and(QPetriNet.petriNet.version.patch.loe(1)))); + + compareMongoQueries(mongoDbUtils, actual, expected); + + actual = evaluateQuery("process: version gt 1.1.1").getFullMongoQuery(); + expected = QPetriNet.petriNet.version.major.gt(1) + .or(QPetriNet.petriNet.version.major.eq(1L).and(QPetriNet.petriNet.version.minor.gt(1))) + .or(QPetriNet.petriNet.version.major.eq(1L).and(QPetriNet.petriNet.version.minor.eq(1L).and(QPetriNet.petriNet.version.patch.gt(1)))); + + compareMongoQueries(mongoDbUtils, actual, expected); + + actual = evaluateQuery("process: version gte 1.1.1").getFullMongoQuery(); + expected = QPetriNet.petriNet.version.major.goe(1) + .or(QPetriNet.petriNet.version.major.eq(1L).and(QPetriNet.petriNet.version.minor.goe(1))) + .or(QPetriNet.petriNet.version.major.eq(1L).and(QPetriNet.petriNet.version.minor.eq(1L).and(QPetriNet.petriNet.version.patch.goe(1)))); + + compareMongoQueries(mongoDbUtils, actual, expected); + + Version v1 = new Version(1, 1, 1); + Version v2 = new Version(2, 2, 2); + Version v3 = new Version(3, 3, 3); + actual = evaluateQuery("process: version in (1.1.1, 2.2.2, 3.3.3)").getFullMongoQuery(); + expected = QPetriNet.petriNet.version.in(List.of(v1, v2, v3)); + + compareMongoQueries(mongoDbUtils, actual, expected); + + actual = evaluateQuery("process: version not in (1.1.1, 2.2.2, 3.3.3)").getFullMongoQuery(); + expected = QPetriNet.petriNet.version.in(List.of(v1, v2, v3)).not(); + + compareMongoQueries(mongoDbUtils, actual, expected); + + actual = evaluateQuery("process: version in (1.1.1 : 2.2.2)").getFullMongoQuery(); + BooleanBuilder builder = new BooleanBuilder(); + builder.and(QPetriNet.petriNet.version.major.gt(1) + .or(QPetriNet.petriNet.version.major.eq(1L).and(QPetriNet.petriNet.version.minor.gt(1))) + .or(QPetriNet.petriNet.version.major.eq(1L).and(QPetriNet.petriNet.version.minor.eq(1L).and(QPetriNet.petriNet.version.patch.gt(1))))); + builder.and(QPetriNet.petriNet.version.major.lt(2) + .or(QPetriNet.petriNet.version.major.eq(2L).and(QPetriNet.petriNet.version.minor.lt(2))) + .or(QPetriNet.petriNet.version.major.eq(2L).and(QPetriNet.petriNet.version.minor.eq(2L).and(QPetriNet.petriNet.version.patch.lt(2))))); + expected = builder; + + compareMongoQueries(mongoDbUtils, actual, expected); + + actual = evaluateQuery("process: version in [1.1.1 : 2.2.2]").getFullMongoQuery(); + builder = new BooleanBuilder(); + builder.and(QPetriNet.petriNet.version.major.goe(1) + .or(QPetriNet.petriNet.version.major.eq(1L).and(QPetriNet.petriNet.version.minor.goe(1))) + .or(QPetriNet.petriNet.version.major.eq(1L).and(QPetriNet.petriNet.version.minor.eq(1L).and(QPetriNet.petriNet.version.patch.goe(1))))); + builder.and(QPetriNet.petriNet.version.major.loe(2) + .or(QPetriNet.petriNet.version.major.eq(2L).and(QPetriNet.petriNet.version.minor.loe(2))) + .or(QPetriNet.petriNet.version.major.eq(2L).and(QPetriNet.petriNet.version.minor.eq(2L).and(QPetriNet.petriNet.version.patch.loe(2))))); + expected = builder; + + compareMongoQueries(mongoDbUtils, actual, expected); + + actual = evaluateQuery("process: version not in (1.1.1 : 2.2.2]").getFullMongoQuery(); + builder = new BooleanBuilder(); + builder.and(QPetriNet.petriNet.version.major.gt(1) + .or(QPetriNet.petriNet.version.major.eq(1L).and(QPetriNet.petriNet.version.minor.gt(1))) + .or(QPetriNet.petriNet.version.major.eq(1L).and(QPetriNet.petriNet.version.minor.eq(1L).and(QPetriNet.petriNet.version.patch.gt(1))))); + builder.and(QPetriNet.petriNet.version.major.loe(2) + .or(QPetriNet.petriNet.version.major.eq(2L).and(QPetriNet.petriNet.version.minor.loe(2))) + .or(QPetriNet.petriNet.version.major.eq(2L).and(QPetriNet.petriNet.version.minor.eq(2L).and(QPetriNet.petriNet.version.patch.loe(2))))); + expected = builder.not(); + + compareMongoQueries(mongoDbUtils, actual, expected); + + // title comparison + checkStringComparison(mongoDbUtils, "process", "title", QPetriNet.petriNet.title.defaultValue); + + // creationDate comparison + checkDateComparison(mongoDbUtils, "process", "creationDate", QPetriNet.petriNet.creationDate); + } + + @Test + public void testComplexMongodbProcessQuery() { + MongoDbUtils mongoDbUtils = new MongoDbUtils<>(mongoOperations, PetriNet.class); + + // not comparison + Predicate actual = evaluateQuery(String.format("process: id not eq '%s'", GENERIC_OBJECT_ID)).getFullMongoQuery(); + Predicate expected = QPetriNet.petriNet.id.eq(GENERIC_OBJECT_ID).not(); + + compareMongoQueries(mongoDbUtils, actual, expected); + + // and comparison + actual = evaluateQuery(String.format("process: id eq '%s' and title eq 'test'", GENERIC_OBJECT_ID)).getFullMongoQuery(); + expected = QPetriNet.petriNet.id.eq(GENERIC_OBJECT_ID).and(QPetriNet.petriNet.title.defaultValue.eq("test")); + + compareMongoQueries(mongoDbUtils, actual, expected); + + // and not comparison + actual = evaluateQuery(String.format("process: id eq '%s' and title not eq 'test'", GENERIC_OBJECT_ID)).getFullMongoQuery(); + expected = QPetriNet.petriNet.id.eq(GENERIC_OBJECT_ID).and(QPetriNet.petriNet.title.defaultValue.eq("test").not()); + + compareMongoQueries(mongoDbUtils, actual, expected); + + // or comparison + actual = evaluateQuery(String.format("process: id eq '%s' or title eq 'test'", GENERIC_OBJECT_ID)).getFullMongoQuery(); + expected = QPetriNet.petriNet.id.eq(GENERIC_OBJECT_ID).or(QPetriNet.petriNet.title.defaultValue.eq("test")); + + compareMongoQueries(mongoDbUtils, actual, expected); + + // or not comparison + actual = evaluateQuery(String.format("process: id eq '%s' or title not eq 'test'", GENERIC_OBJECT_ID)).getFullMongoQuery(); + expected = QPetriNet.petriNet.id.eq(GENERIC_OBJECT_ID).or(QPetriNet.petriNet.title.defaultValue.eq("test").not()); + + compareMongoQueries(mongoDbUtils, actual, expected); + + // parenthesis comparison + actual = evaluateQuery(String.format("process: id eq '%s' and (title eq 'test' or title eq 'test1')", GENERIC_OBJECT_ID)).getFullMongoQuery(); + expected = QPetriNet.petriNet.id.eq(GENERIC_OBJECT_ID).and(QPetriNet.petriNet.title.defaultValue.eq("test").or(QPetriNet.petriNet.title.defaultValue.eq("test1"))); + + compareMongoQueries(mongoDbUtils, actual, expected); + + // parenthesis not comparison + actual = evaluateQuery(String.format("process: id eq '%s' and not (title eq 'test' or title eq 'test1')", GENERIC_OBJECT_ID)).getFullMongoQuery(); + expected = QPetriNet.petriNet.id.eq(GENERIC_OBJECT_ID).and(QPetriNet.petriNet.title.defaultValue.eq("test").or(QPetriNet.petriNet.title.defaultValue.eq("test1")).not()); + + compareMongoQueries(mongoDbUtils, actual, expected); + + // nested parenthesis comparison + actual = evaluateQuery(String.format("process: id eq '%s' and (title eq 'test' or (title eq 'test1' and identifier eq 'test'))", GENERIC_OBJECT_ID)).getFullMongoQuery(); + expected = QPetriNet.petriNet.id.eq(GENERIC_OBJECT_ID) + .and(QPetriNet.petriNet.title.defaultValue.eq("test") + .or(QPetriNet.petriNet.title.defaultValue.eq("test1").and(QPetriNet.petriNet.identifier.eq("test")))); + + compareMongoQueries(mongoDbUtils, actual, expected); + } + + @Test + public void testSimpleMongodbCaseQuery() { + MongoDbUtils mongoDbUtils = new MongoDbUtils<>(mongoOperations, Case.class); + + // id comparison + Predicate actual = evaluateQuery(String.format("case: id eq '%s'", GENERIC_OBJECT_ID)).getFullMongoQuery(); + Predicate expected = QCase.case$.id.eq(GENERIC_OBJECT_ID); + + compareMongoQueries(mongoDbUtils, actual, expected); + + actual = evaluateQuery(String.format("case: id in ('%s', '%s')", GENERIC_OBJECT_ID, GENERIC_OBJECT_ID)).getFullMongoQuery(); + expected = QCase.case$.id.in(GENERIC_OBJECT_ID, GENERIC_OBJECT_ID); + + compareMongoQueries(mongoDbUtils, actual, expected); + + // processId comparison + actual = evaluateQuery(String.format("case: processId eq '%s'", GENERIC_OBJECT_ID)).getFullMongoQuery(); + expected = QCase.case$.petriNetObjectId.eq(GENERIC_OBJECT_ID); + + compareMongoQueries(mongoDbUtils, actual, expected); + + actual = evaluateQuery(String.format("case: processId in ('%s', '%s')", GENERIC_OBJECT_ID, GENERIC_OBJECT_ID)).getFullMongoQuery(); + expected = QCase.case$.petriNetObjectId.in(GENERIC_OBJECT_ID, GENERIC_OBJECT_ID); + + compareMongoQueries(mongoDbUtils, actual, expected); + + // processIdentifier comparison + checkStringComparison(mongoDbUtils, "case", "processIdentifier", QCase.case$.processIdentifier); + + // title comparison + checkStringComparison(mongoDbUtils, "case", "title", QCase.case$.title); + + // creationDate comparison + checkDateComparison(mongoDbUtils, "case", "creationDate", QCase.case$.creationDate); + + // author comparison + actual = evaluateQuery("case: author eq 'test'").getFullMongoQuery(); + expected = QCase.case$.author.id.eq("test"); + + compareMongoQueries(mongoDbUtils, actual, expected); + + actual = evaluateQuery("case: author contains 'test'").getFullMongoQuery(); + expected = QCase.case$.author.id.contains("test"); + + compareMongoQueries(mongoDbUtils, actual, expected); + + actual = evaluateQuery("case: author in ('test', 'test1')").getFullMongoQuery(); + expected = QCase.case$.author.id.in("test", "test1"); + + compareMongoQueries(mongoDbUtils, actual, expected); + + // only available for elastic query + // places comparison + actual = evaluateQuery("case: places.p1.marking eq 1").getFullMongoQuery(); + assert actual == null; + + // task state comparison + actual = evaluateQuery("case: tasks.t1.state eq enabled").getFullMongoQuery(); + assert actual == null; + + // task userId comparison + actual = evaluateQuery("case: tasks.t1.userId eq 'test'").getFullMongoQuery(); + assert actual == null; + + // data value comparison + actual = evaluateQuery("case: data.field1.value eq 'test'").getFullMongoQuery(); + assert actual == null; + + actual = evaluateQuery("case: data.field1.value contains 'test'").getFullMongoQuery(); + assert actual == null; + + actual = evaluateQuery("case: data.field1.value eq 1").getFullMongoQuery(); + assert actual == null; + + actual = evaluateQuery("case: data.field1.value lt 1").getFullMongoQuery(); + assert actual == null; + + actual = evaluateQuery("case: data.field1.value lte 1").getFullMongoQuery(); + assert actual == null; + + actual = evaluateQuery("case: data.field1.value gt 1").getFullMongoQuery(); + assert actual == null; + + actual = evaluateQuery("case: data.field1.value gte 1").getFullMongoQuery(); + assert actual == null; + + actual = evaluateQuery("case: data.field1.value eq 2011-12-03T10:15:30").getFullMongoQuery(); + assert actual == null; + + actual = evaluateQuery("case: data.field1.value lt 2011-12-03T10:15:30").getFullMongoQuery(); + assert actual == null; + + actual = evaluateQuery("case: data.field1.value lte 2011-12-03T10:15:30").getFullMongoQuery(); + assert actual == null; + + actual = evaluateQuery("case: data.field1.value gt 2011-12-03T10:15:30").getFullMongoQuery(); + assert actual == null; + + actual = evaluateQuery("case: data.field1.value gte 2011-12-03T10:15:30").getFullMongoQuery(); + assert actual == null; + + actual = evaluateQuery("case: data.field1.value eq true").getFullMongoQuery(); + assert actual == null; + + // data options comparison + actual = evaluateQuery("case: data.field1.options eq 'test'").getFullMongoQuery(); + assert actual == null; + + actual = evaluateQuery("case: data.field1.options contains 'test'").getFullMongoQuery(); + assert actual == null; + } + + @Test + public void testComplexMongodbCaseQuery() { + MongoDbUtils mongoDbUtils = new MongoDbUtils<>(mongoOperations, Case.class); + + // not comparison + Predicate actual = evaluateQuery(String.format("case: id not eq '%s'", GENERIC_OBJECT_ID)).getFullMongoQuery(); + Predicate expected = QCase.case$.id.eq(GENERIC_OBJECT_ID).not(); + + compareMongoQueries(mongoDbUtils, actual, expected); + + // and comparison + actual = evaluateQuery(String.format("case: id eq '%s' and title eq 'test'", GENERIC_OBJECT_ID)).getFullMongoQuery(); + expected = QCase.case$.id.eq(GENERIC_OBJECT_ID).and(QCase.case$.title.eq("test")); + + compareMongoQueries(mongoDbUtils, actual, expected); + + // and not comparison + actual = evaluateQuery(String.format("case: id eq '%s' and title not eq 'test'", GENERIC_OBJECT_ID)).getFullMongoQuery(); + expected = QCase.case$.id.eq(GENERIC_OBJECT_ID).and(QCase.case$.title.eq("test").not()); + + compareMongoQueries(mongoDbUtils, actual, expected); + + // or comparison + actual = evaluateQuery(String.format("case: id eq '%s' or title eq 'test'", GENERIC_OBJECT_ID)).getFullMongoQuery(); + expected = QCase.case$.id.eq(GENERIC_OBJECT_ID).or(QCase.case$.title.eq("test")); + + compareMongoQueries(mongoDbUtils, actual, expected); + + // or not comparison + actual = evaluateQuery(String.format("case: id eq '%s' or title not eq 'test'", GENERIC_OBJECT_ID)).getFullMongoQuery(); + expected = QCase.case$.id.eq(GENERIC_OBJECT_ID).or(QCase.case$.title.eq("test").not()); + + compareMongoQueries(mongoDbUtils, actual, expected); + + // parenthesis comparison + actual = evaluateQuery(String.format("case: id eq '%s' and (title eq 'test' or title eq 'test1')", GENERIC_OBJECT_ID)).getFullMongoQuery(); + expected = QCase.case$.id.eq(GENERIC_OBJECT_ID).and(QCase.case$.title.eq("test").or(QCase.case$.title.eq("test1"))); + + compareMongoQueries(mongoDbUtils, actual, expected); + + // nested parenthesis comparison + actual = evaluateQuery(String.format("case: id eq '%s' and (title eq 'test' or (title eq 'test1' and processIdentifier eq 'test'))", GENERIC_OBJECT_ID)).getFullMongoQuery(); + expected = QCase.case$.id.eq(GENERIC_OBJECT_ID) + .and(QCase.case$.title.eq("test") + .or(QCase.case$.title.eq("test1").and(QCase.case$.processIdentifier.eq("test")))); + + compareMongoQueries(mongoDbUtils, actual, expected); + } + + @Test + public void testSimpleMongodbTaskQuery() { + MongoDbUtils mongoDbUtils = new MongoDbUtils<>(mongoOperations, Task.class); + + // id comparison + Predicate actual = evaluateQuery(String.format("task: id eq '%s'", GENERIC_OBJECT_ID)).getFullMongoQuery(); + Predicate expected = QTask.task.id.eq(GENERIC_OBJECT_ID); + + compareMongoQueries(mongoDbUtils, actual, expected); + + actual = evaluateQuery(String.format("task: id in ('%s', '%s')", GENERIC_OBJECT_ID, GENERIC_OBJECT_ID)).getFullMongoQuery(); + expected = QTask.task.id.in(GENERIC_OBJECT_ID, GENERIC_OBJECT_ID); + + compareMongoQueries(mongoDbUtils, actual, expected); + + // transitionId comparison + checkStringComparison(mongoDbUtils, "task", "transitionId", QTask.task.transitionId); + + // title comparison + checkStringComparison(mongoDbUtils, "task", "title", QTask.task.title.defaultValue); + + // state comparison + actual = evaluateQuery("task: state eq enabled").getFullMongoQuery(); + expected = QTask.task.state.eq(State.ENABLED); + + compareMongoQueries(mongoDbUtils, actual, expected); + + actual = evaluateQuery("task: state eq disabled").getFullMongoQuery(); + expected = QTask.task.state.eq(State.DISABLED); + + compareMongoQueries(mongoDbUtils, actual, expected); + + // userId comparison + actual = evaluateQuery("task: userId eq 'test'").getFullMongoQuery(); + expected = QTask.task.userId.eq("test"); + + compareMongoQueries(mongoDbUtils, actual, expected); + + actual = evaluateQuery("task: userId contains 'test'").getFullMongoQuery(); + expected = QTask.task.userId.contains("test"); + + compareMongoQueries(mongoDbUtils, actual, expected); + + actual = evaluateQuery("task: userId in ('test', 'test1')").getFullMongoQuery(); + expected = QTask.task.userId.in("test", "test1"); + + compareMongoQueries(mongoDbUtils, actual, expected); + + // caseId comparison + actual = evaluateQuery("task: caseId eq 'test'").getFullMongoQuery(); + expected = QTask.task.caseId.eq("test"); + + compareMongoQueries(mongoDbUtils, actual, expected); + + actual = evaluateQuery("task: caseId contains 'test'").getFullMongoQuery(); + expected = QTask.task.caseId.contains("test"); + + compareMongoQueries(mongoDbUtils, actual, expected); + + actual = evaluateQuery("task: caseId in ('test', 'test1')").getFullMongoQuery(); + expected = QTask.task.caseId.in("test", "test1"); + + compareMongoQueries(mongoDbUtils, actual, expected); + + // processId comparison + actual = evaluateQuery("task: processId eq 'test'").getFullMongoQuery(); + expected = QTask.task.processId.eq("test"); + + compareMongoQueries(mongoDbUtils, actual, expected); + + actual = evaluateQuery("task: processId contains 'test'").getFullMongoQuery(); + expected = QTask.task.processId.contains("test"); + + compareMongoQueries(mongoDbUtils, actual, expected); + + actual = evaluateQuery("task: processId in ('test', 'test1')").getFullMongoQuery(); + expected = QTask.task.processId.in("test", "test1"); + + compareMongoQueries(mongoDbUtils, actual, expected); + + // lastAssign comparison + checkDateComparison(mongoDbUtils, "task", "lastAssign", QTask.task.lastAssigned); + + // lastFinish comparison + checkDateComparison(mongoDbUtils, "task", "lastFinish", QTask.task.lastFinished); + } + + @Test + public void testComplexMongodbTaskQuery() { + MongoDbUtils mongoDbUtils = new MongoDbUtils<>(mongoOperations, Task.class); + + // not comparison + Predicate actual = evaluateQuery(String.format("task: id not eq '%s'", GENERIC_OBJECT_ID)).getFullMongoQuery(); + Predicate expected = QTask.task.id.eq(GENERIC_OBJECT_ID).not(); + + compareMongoQueries(mongoDbUtils, actual, expected); + + // and comparison + actual = evaluateQuery(String.format("task: id eq '%s' and title eq 'test'", GENERIC_OBJECT_ID)).getFullMongoQuery(); + expected = QTask.task.id.eq(GENERIC_OBJECT_ID).and(QTask.task.title.defaultValue.eq("test")); + + compareMongoQueries(mongoDbUtils, actual, expected); + + // and not comparison + actual = evaluateQuery(String.format("task: id eq '%s' and title not eq 'test'", GENERIC_OBJECT_ID)).getFullMongoQuery(); + expected = QTask.task.id.eq(GENERIC_OBJECT_ID).and(QTask.task.title.defaultValue.eq("test").not()); + + compareMongoQueries(mongoDbUtils, actual, expected); + + // or comparison + actual = evaluateQuery(String.format("task: id eq '%s' or title eq 'test'", GENERIC_OBJECT_ID)).getFullMongoQuery(); + expected = QTask.task.id.eq(GENERIC_OBJECT_ID).or(QTask.task.title.defaultValue.eq("test")); + + compareMongoQueries(mongoDbUtils, actual, expected); + + // or not comparison + actual = evaluateQuery(String.format("task: id eq '%s' or title not eq 'test'", GENERIC_OBJECT_ID)).getFullMongoQuery(); + expected = QTask.task.id.eq(GENERIC_OBJECT_ID).or(QTask.task.title.defaultValue.eq("test").not()); + + compareMongoQueries(mongoDbUtils, actual, expected); + + // parenthesis comparison + actual = evaluateQuery(String.format("task: id eq '%s' and (title eq 'test' or title eq 'test1')", GENERIC_OBJECT_ID)).getFullMongoQuery(); + expected = QTask.task.id.eq(GENERIC_OBJECT_ID).and(QTask.task.title.defaultValue.eq("test").or(QTask.task.title.defaultValue.eq("test1"))); + + compareMongoQueries(mongoDbUtils, actual, expected); + + // parenthesis not comparison + actual = evaluateQuery(String.format("task: id eq '%s' and not (title eq 'test' or title eq 'test1')", GENERIC_OBJECT_ID)).getFullMongoQuery(); + expected = QTask.task.id.eq(GENERIC_OBJECT_ID).and(QTask.task.title.defaultValue.eq("test").or(QTask.task.title.defaultValue.eq("test1")).not()); + + compareMongoQueries(mongoDbUtils, actual, expected); + + // nested parenthesis comparison + actual = evaluateQuery(String.format("task: id eq '%s' and (title eq 'test' or (title eq 'test1' and processId eq 'test'))", GENERIC_OBJECT_ID)).getFullMongoQuery(); + expected = QTask.task.id.eq(GENERIC_OBJECT_ID) + .and(QTask.task.title.defaultValue.eq("test") + .or(QTask.task.title.defaultValue.eq("test1").and(QTask.task.processId.eq("test")))); + + compareMongoQueries(mongoDbUtils, actual, expected); + } + + @Test + public void testSimpleMongodbUserQuery() { + MongoDbUtils mongoDbUtils = new MongoDbUtils<>(mongoOperations, User.class); + + // id comparison + Predicate actual = evaluateQuery(String.format("user: id eq '%s'", GENERIC_OBJECT_ID)).getFullMongoQuery(); + Predicate expected = QUser.user.id.eq(GENERIC_OBJECT_ID); + + compareMongoQueries(mongoDbUtils, actual, expected); + + actual = evaluateQuery(String.format("user: id in ('%s', '%s')", GENERIC_OBJECT_ID, GENERIC_OBJECT_ID)).getFullMongoQuery(); + expected = QUser.user.id.in(GENERIC_OBJECT_ID, GENERIC_OBJECT_ID); + + compareMongoQueries(mongoDbUtils, actual, expected); + + // name comparison + checkStringComparison(mongoDbUtils, "user", "name", QUser.user.name); + + // surname comparison + checkStringComparison(mongoDbUtils, "user", "surname", QUser.user.surname); + + // email comparison + checkStringComparison(mongoDbUtils, "user", "email", QUser.user.email); + } + + @Test + public void testComplexMongodbUserQuery() { + MongoDbUtils mongoDbUtils = new MongoDbUtils<>(mongoOperations, User.class); + + // not comparison + Predicate actual = evaluateQuery(String.format("user: id not eq '%s'", GENERIC_OBJECT_ID)).getFullMongoQuery(); + Predicate expected = QUser.user.id.eq(GENERIC_OBJECT_ID).not(); + + compareMongoQueries(mongoDbUtils, actual, expected); + + // and comparison + actual = evaluateQuery(String.format("user: id eq '%s' and email eq 'test'", GENERIC_OBJECT_ID)).getFullMongoQuery(); + expected = QUser.user.id.eq(GENERIC_OBJECT_ID).and(QUser.user.email.eq("test")); + + compareMongoQueries(mongoDbUtils, actual, expected); + + // and not comparison + actual = evaluateQuery(String.format("user: id eq '%s' and email not eq 'test'", GENERIC_OBJECT_ID)).getFullMongoQuery(); + expected = QUser.user.id.eq(GENERIC_OBJECT_ID).and(QUser.user.email.eq("test").not()); + + compareMongoQueries(mongoDbUtils, actual, expected); + + // or comparison + actual = evaluateQuery(String.format("user: id eq '%s' or email eq 'test'", GENERIC_OBJECT_ID)).getFullMongoQuery(); + expected = QUser.user.id.eq(GENERIC_OBJECT_ID).or(QUser.user.email.eq("test")); + + compareMongoQueries(mongoDbUtils, actual, expected); + + // or not comparison + actual = evaluateQuery(String.format("user: id eq '%s' or email not eq 'test'", GENERIC_OBJECT_ID)).getFullMongoQuery(); + expected = QUser.user.id.eq(GENERIC_OBJECT_ID).or(QUser.user.email.eq("test").not()); + + compareMongoQueries(mongoDbUtils, actual, expected); + + // parenthesis comparison + actual = evaluateQuery(String.format("user: id eq '%s' and (email eq 'test' or email eq 'test1')", GENERIC_OBJECT_ID)).getFullMongoQuery(); + expected = QUser.user.id.eq(GENERIC_OBJECT_ID).and(QUser.user.email.eq("test").or(QUser.user.email.eq("test1"))); + + compareMongoQueries(mongoDbUtils, actual, expected); + + // parenthesis not comparison + actual = evaluateQuery(String.format("user: id eq '%s' and not (email eq 'test' or email eq 'test1')", GENERIC_OBJECT_ID)).getFullMongoQuery(); + expected = QUser.user.id.eq(GENERIC_OBJECT_ID).and(QUser.user.email.eq("test").or(QUser.user.email.eq("test1")).not()); + + compareMongoQueries(mongoDbUtils, actual, expected); + + // nested parenthesis comparison + actual = evaluateQuery(String.format("user: id eq '%s' and (email eq 'test' or (email eq 'test1' and name eq 'test'))", GENERIC_OBJECT_ID)).getFullMongoQuery(); + expected = QUser.user.id.eq(GENERIC_OBJECT_ID) + .and(QUser.user.email.eq("test") + .or(QUser.user.email.eq("test1").and(QUser.user.name.eq("test")))); + + compareMongoQueries(mongoDbUtils, actual, expected); + } + + @Test + public void testSimpleElasticProcessQuery() { + // elastic query should be always null + // id comparison + String actual = evaluateQuery(String.format("process: id eq '%s'", GENERIC_OBJECT_ID)).getFullElasticQuery(); + assert actual == null; + + // identifier comparison + actual = evaluateQuery("process: identifier eq 'test'").getFullElasticQuery(); + assert actual == null; + + actual = evaluateQuery("process: identifier contains 'test'").getFullElasticQuery(); + assert actual == null; + + // version comparison + actual = evaluateQuery("process: version eq 1.1.1").getFullElasticQuery(); + assert actual == null; + + actual = evaluateQuery("process: version lt 1.1.1").getFullElasticQuery(); + assert actual == null; + + actual = evaluateQuery("process: version lte 1.1.1").getFullElasticQuery(); + assert actual == null; + + + actual = evaluateQuery("process: version gt 1.1.1").getFullElasticQuery(); + assert actual == null; + + + actual = evaluateQuery("process: version gte 1.1.1").getFullElasticQuery(); + assert actual == null; + + + // title comparison + actual = evaluateQuery("process: title eq 'test'").getFullElasticQuery(); + assert actual == null; + + + actual = evaluateQuery("process: title contains 'test'").getFullElasticQuery(); + assert actual == null; + + + // creationDate comparison + actual = evaluateQuery("process: creationDate eq 2011-12-03T10:15:30").getFullElasticQuery(); + assert actual == null; + + + actual = evaluateQuery("process: creationDate lt 2011-12-03T10:15:30").getFullElasticQuery(); + assert actual == null; + + + actual = evaluateQuery("process: creationDate lte 2011-12-03T10:15:30").getFullElasticQuery(); + assert actual == null; + + + actual = evaluateQuery("process: creationDate gt 2011-12-03T10:15:30").getFullElasticQuery(); + assert actual == null; + + + actual = evaluateQuery("process: creationDate gte 2011-12-03T10:15:30").getFullElasticQuery(); + assert actual == null; + } + + @Test + public void testComplexElasticProcessQuery() { + // elastic query should be always null + // not comparison + String actual = evaluateQuery(String.format("process: id not eq '%s'", GENERIC_OBJECT_ID)).getFullElasticQuery(); + assert actual == null; + + // and comparison + actual = evaluateQuery(String.format("process: id eq '%s' and title eq 'test'", GENERIC_OBJECT_ID)).getFullElasticQuery(); + assert actual == null; + + // and not comparison + actual = evaluateQuery(String.format("process: id eq '%s' and title not eq 'test'", GENERIC_OBJECT_ID)).getFullElasticQuery(); + assert actual == null; + + // or comparison + actual = evaluateQuery(String.format("process: id eq '%s' or title eq 'test'", GENERIC_OBJECT_ID)).getFullElasticQuery(); + assert actual == null; + + // or not comparison + actual = evaluateQuery(String.format("process: id eq '%s' or title not eq 'test'", GENERIC_OBJECT_ID)).getFullElasticQuery(); + assert actual == null; + + // parenthesis comparison + actual = evaluateQuery(String.format("process: id eq '%s' and (title eq 'test' or title eq 'test1')", GENERIC_OBJECT_ID)).getFullElasticQuery(); + assert actual == null; + + // parenthesis not comparison + actual = evaluateQuery(String.format("process: id eq '%s' and not (title eq 'test' or title eq 'test1')", GENERIC_OBJECT_ID)).getFullElasticQuery(); + assert actual == null; + + + // nested parenthesis comparison + actual = evaluateQuery(String.format("process: id eq '%s' and (title eq 'test' or (title eq 'test1' and identifier eq 'test'))", GENERIC_OBJECT_ID)).getFullElasticQuery(); + assert actual == null; + } + + @Test + public void testSimpleElasticCaseQuery() { + // id comparison + String actual = evaluateQuery(String.format("case: id eq '%s'", GENERIC_OBJECT_ID)).getFullElasticQuery(); + String expected = String.format("stringId:%s", GENERIC_OBJECT_ID); + assert expected.equals(actual); + + actual = evaluateQuery(String.format("case: id in ('%s', '%s')", GENERIC_OBJECT_ID, GENERIC_OBJECT_ID)).getFullElasticQuery(); + expected = String.format("stringId:(%s OR %s)", GENERIC_OBJECT_ID, GENERIC_OBJECT_ID); + assert expected.equals(actual); + + // processId comparison + actual = evaluateQuery(String.format("case: processId eq '%s'", GENERIC_OBJECT_ID)).getFullElasticQuery(); + expected = String.format("processId:%s", GENERIC_OBJECT_ID); + assert expected.equals(actual); + + actual = evaluateQuery(String.format("case: processId in ('%s', '%s')", GENERIC_OBJECT_ID, GENERIC_OBJECT_ID)).getFullElasticQuery(); + expected = String.format("processId:(%s OR %s)", GENERIC_OBJECT_ID, GENERIC_OBJECT_ID); + assert expected.equals(actual); + + // processIdentifier comparison + checkStringComparisonElastic("case", "processIdentifier", "processIdentifier"); + + // title comparison + checkStringComparisonElastic("case", "title", "title"); + + // creationDate comparison + checkDateComparisonElastic("case", "creationDate", "creationDateSortable"); + + // author comparison + actual = evaluateQuery("case: author eq 'test'").getFullElasticQuery(); + expected = "author:test"; + assert expected.equals(actual); + + actual = evaluateQuery("case: author contains 'test'").getFullElasticQuery(); + expected = "author:*test*"; + assert expected.equals(actual); + + actual = evaluateQuery(String.format("case: author in ('%s', '%s')", GENERIC_OBJECT_ID, GENERIC_OBJECT_ID)).getFullElasticQuery(); + expected = String.format("author:(%s OR %s)", GENERIC_OBJECT_ID, GENERIC_OBJECT_ID); + assert expected.equals(actual); + + // places comparison + checkNumberComparisonElastic("case", "places.p1.marking", "places.p1.marking"); + + // task state comparison + actual = evaluateQuery("case: tasks.t1.state eq enabled").getFullElasticQuery(); + expected = String.format("tasks.t1.state:%s", State.ENABLED); + assert expected.equals(actual); + + actual = evaluateQuery("case: tasks.t1.state eq disabled").getFullElasticQuery(); + expected = String.format("tasks.t1.state:%s", State.DISABLED); + assert expected.equals(actual); + + // task userId comparison + actual = evaluateQuery("case: tasks.t1.userId eq 'test'").getFullElasticQuery(); + expected = "tasks.t1.userId:test"; + assert expected.equals(actual); + + actual = evaluateQuery("case: tasks.t1.userId contains 'test'").getFullElasticQuery(); + expected = "tasks.t1.userId:*test*"; + assert expected.equals(actual); + + actual = evaluateQuery(String.format("case: tasks.t1.userId in ('%s', '%s')", GENERIC_OBJECT_ID, GENERIC_OBJECT_ID)).getFullElasticQuery(); + expected = String.format("tasks.t1.userId:(%s OR %s)", GENERIC_OBJECT_ID, GENERIC_OBJECT_ID); + assert expected.equals(actual); + + // data value comparison + checkStringComparisonElastic("case", "data.field1.value", "dataSet.field1.textValue"); + + checkNumberComparisonElastic("case", "data.field2.value", "dataSet.field2.numberValue"); + + checkDateComparisonElastic("case", "data.field3.value", "dataSet.field3.timestampValue"); + + actual = evaluateQuery("case: data.field1.value eq true").getFullElasticQuery(); + expected = "dataSet.field1.booleanValue:true"; + assert expected.equals(actual); + + actual = evaluateQuery("case: data.field1.value eq false").getFullElasticQuery(); + expected = "dataSet.field1.booleanValue:false"; + assert expected.equals(actual); + + // data options comparison + checkStringComparisonElastic("case", "data.field1.options", "dataSet.field1.options"); + } + + @Test + public void testComplexElasticCaseQuery() { + // not comparison + String actual = evaluateQuery(String.format("case: id not eq '%s'", GENERIC_OBJECT_ID)).getFullElasticQuery(); + String expected = String.format("NOT stringId:%s", GENERIC_OBJECT_ID); + assert expected.equals(actual); + + // and comparison + actual = evaluateQuery(String.format("case: id eq '%s' and title eq 'test'", GENERIC_OBJECT_ID)).getFullElasticQuery(); + expected = String.format("stringId:%s AND title:test", GENERIC_OBJECT_ID); + assert expected.equals(actual); + + // and not comparison + actual = evaluateQuery(String.format("case: id eq '%s' and title not eq 'test'", GENERIC_OBJECT_ID)).getFullElasticQuery(); + expected = String.format("stringId:%s AND NOT title:test", GENERIC_OBJECT_ID); + assert expected.equals(actual); + + // or comparison + actual = evaluateQuery(String.format("case: id eq '%s' or title eq 'test'", GENERIC_OBJECT_ID)).getFullElasticQuery(); + expected = String.format("stringId:%s OR title:test", GENERIC_OBJECT_ID); + assert expected.equals(actual); + + // or not comparison + actual = evaluateQuery(String.format("case: id eq '%s' or title not eq 'test'", GENERIC_OBJECT_ID)).getFullElasticQuery(); + expected = String.format("stringId:%s OR NOT title:test", GENERIC_OBJECT_ID); + assert expected.equals(actual); + + // parenthesis comparison + actual = evaluateQuery(String.format("case: id eq '%s' and (title eq 'test' or title eq 'test1')", GENERIC_OBJECT_ID)).getFullElasticQuery(); + expected = String.format("stringId:%s AND (title:test OR title:test1)", GENERIC_OBJECT_ID); + assert expected.equals(actual); + + // nested parenthesis comparison + actual = evaluateQuery(String.format("case: id eq '%s' and (title eq 'test' or (title eq 'test1' and processIdentifier eq 'test'))", GENERIC_OBJECT_ID)).getFullElasticQuery(); + expected = String.format("stringId:%s AND (title:test OR (title:test1 AND processIdentifier:test))", GENERIC_OBJECT_ID); + assert expected.equals(actual); + } + + @Test + public void testSimpleElasticTaskQuery() { + // elastic query should be always null + // id comparison + String actual = evaluateQuery(String.format("task: id eq '%s'", GENERIC_OBJECT_ID)).getFullElasticQuery(); + assert actual == null; + + // transitionId comparison + actual = evaluateQuery("task: transitionId eq 'test'").getFullElasticQuery(); + assert actual == null; + + actual = evaluateQuery("task: transitionId contains 'test'").getFullElasticQuery(); + assert actual == null; + + // title comparison + actual = evaluateQuery("task: title eq 'test'").getFullElasticQuery(); + assert actual == null; + + actual = evaluateQuery("task: title contains 'test'").getFullElasticQuery(); + assert actual == null; + + // state comparison + actual = evaluateQuery("task: state eq enabled").getFullElasticQuery(); + assert actual == null; + + actual = evaluateQuery("task: state eq disabled").getFullElasticQuery(); + assert actual == null; + + // userId comparison + actual = evaluateQuery("task: userId eq 'test'").getFullElasticQuery(); + assert actual == null; + + actual = evaluateQuery("task: userId contains 'test'").getFullElasticQuery(); + assert actual == null; + + // caseId comparison + actual = evaluateQuery("task: caseId eq 'test'").getFullElasticQuery(); + assert actual == null; + + actual = evaluateQuery("task: caseId contains 'test'").getFullElasticQuery(); + assert actual == null; + + // processId comparison + actual = evaluateQuery("task: processId eq 'test'").getFullElasticQuery(); + assert actual == null; + + actual = evaluateQuery("task: processId contains 'test'").getFullElasticQuery(); + assert actual == null; + + // lastAssign comparison + actual = evaluateQuery("task: lastAssign eq 2011-12-03T10:15:30").getFullElasticQuery(); + assert actual == null; + + actual = evaluateQuery("task: lastAssign lt 2011-12-03T10:15:30").getFullElasticQuery(); + assert actual == null; + + actual = evaluateQuery("task: lastAssign lte 2011-12-03T10:15:30").getFullElasticQuery(); + assert actual == null; + + actual = evaluateQuery("task: lastAssign gt 2011-12-03T10:15:30").getFullElasticQuery(); + assert actual == null; + + actual = evaluateQuery("task: lastAssign gte 2011-12-03T10:15:30").getFullElasticQuery(); + assert actual == null; + + // lastFinish comparison + actual = evaluateQuery("task: lastFinish eq 2011-12-03T10:15:30").getFullElasticQuery(); + assert actual == null; + + actual = evaluateQuery("task: lastFinish lt 2011-12-03T10:15:30").getFullElasticQuery(); + assert actual == null; + + actual = evaluateQuery("task: lastFinish lte 2011-12-03T10:15:30").getFullElasticQuery(); + assert actual == null; + + actual = evaluateQuery("task: lastFinish gt 2011-12-03T10:15:30").getFullElasticQuery(); + assert actual == null; + + actual = evaluateQuery("task: lastFinish gte 2011-12-03T10:15:30").getFullElasticQuery(); + assert actual == null; + } + + @Test + public void testComplexElasticTaskQuery() { + // elastic query should be always null + // not comparison + String actual = evaluateQuery(String.format("task: id not eq '%s'", GENERIC_OBJECT_ID)).getFullElasticQuery(); + assert actual == null; + + // and comparison + actual = evaluateQuery(String.format("task: id eq '%s' and title eq 'test'", GENERIC_OBJECT_ID)).getFullElasticQuery(); + assert actual == null; + + // and not comparison + actual = evaluateQuery(String.format("task: id eq '%s' and title not eq 'test'", GENERIC_OBJECT_ID)).getFullElasticQuery(); + assert actual == null; + + // or comparison + actual = evaluateQuery(String.format("task: id eq '%s' or title eq 'test'", GENERIC_OBJECT_ID)).getFullElasticQuery(); + assert actual == null; + + // or not comparison + actual = evaluateQuery(String.format("task: id eq '%s' or title not eq 'test'", GENERIC_OBJECT_ID)).getFullElasticQuery(); + assert actual == null; + + // parenthesis comparison + actual = evaluateQuery(String.format("task: id eq '%s' and (title eq 'test' or title eq 'test1')", GENERIC_OBJECT_ID)).getFullElasticQuery(); + assert actual == null; + + // parenthesis not comparison + actual = evaluateQuery(String.format("task: id eq '%s' and not (title eq 'test' or title eq 'test1')", GENERIC_OBJECT_ID)).getFullElasticQuery(); + assert actual == null; + + // nested parenthesis comparison + actual = evaluateQuery(String.format("task: id eq '%s' and (title eq 'test' or (title eq 'test1' and processId eq 'test'))", GENERIC_OBJECT_ID)).getFullElasticQuery(); + assert actual == null; + } + + @Test + public void testSimpleElasticUserQuery() { + // elastic query should be always null + // id comparison + String actual = evaluateQuery(String.format("user: id eq '%s'", GENERIC_OBJECT_ID)).getFullElasticQuery(); + assert actual == null; + + // name comparison + actual = evaluateQuery("user: name eq 'test'").getFullElasticQuery(); + assert actual == null; + + actual = evaluateQuery("user: name contains 'test'").getFullElasticQuery(); + assert actual == null; + + // surname comparison + actual = evaluateQuery("user: surname eq 'test'").getFullElasticQuery(); + assert actual == null; + + actual = evaluateQuery("user: surname contains 'test'").getFullElasticQuery(); + assert actual == null; + + // email comparison + actual = evaluateQuery("user: email eq 'test'").getFullElasticQuery(); + assert actual == null; + + actual = evaluateQuery("user: email contains 'test'").getFullElasticQuery(); + assert actual == null; + } + + @Test + public void testComplexElasticUserQuery() { + // elastic query should be always null + // not comparison + String actual = evaluateQuery(String.format("user: id not eq '%s'", GENERIC_OBJECT_ID)).getFullElasticQuery(); + assert actual == null; + + // and comparison + actual = evaluateQuery(String.format("user: id eq '%s' and email eq 'test'", GENERIC_OBJECT_ID)).getFullElasticQuery(); + assert actual == null; + + // and not comparison + actual = evaluateQuery(String.format("user: id eq '%s' and email not eq 'test'", GENERIC_OBJECT_ID)).getFullElasticQuery(); + assert actual == null; + + // or comparison + actual = evaluateQuery(String.format("user: id eq '%s' or email eq 'test'", GENERIC_OBJECT_ID)).getFullElasticQuery(); + assert actual == null; + + // or not comparison + actual = evaluateQuery(String.format("user: id eq '%s' or email not eq 'test'", GENERIC_OBJECT_ID)).getFullElasticQuery(); + assert actual == null; + + // parenthesis comparison + actual = evaluateQuery(String.format("user: id eq '%s' and (email eq 'test' or email eq 'test1')", GENERIC_OBJECT_ID)).getFullElasticQuery(); + assert actual == null; + + // parenthesis not comparison + actual = evaluateQuery(String.format("user: id eq '%s' and not (email eq 'test' or email eq 'test1')", GENERIC_OBJECT_ID)).getFullElasticQuery(); + assert actual == null; + + // nested parenthesis comparison + actual = evaluateQuery(String.format("user: id eq '%s' and (email eq 'test' or (email eq 'test1' and name eq 'test'))", GENERIC_OBJECT_ID)).getFullElasticQuery(); + assert actual == null; + } + + @Test + public void testPagingQuery() { + // default + Pageable pageable = evaluateQuery("cases: processIdentifier eq 'test'").getPageable(); + assert pageable.getPageNumber() == 0; + assert pageable.getPageSize() == 20; + + // page number only + pageable = evaluateQuery("cases: processIdentifier eq 'test' page 2").getPageable(); + assert pageable.getPageNumber() == 2; + assert pageable.getPageSize() == 20; + + // page number and page size + pageable = evaluateQuery("cases: processIdentifier eq 'test' page 2 size 4").getPageable(); + assert pageable.getPageNumber() == 2; + assert pageable.getPageSize() == 4; + } + + @Test + public void testProcessSortingQuery() { + // default (no sort) + Pageable actual = evaluateQuery("processes: identifier eq 'test'").getPageable(); + assert !actual.getSort().isSorted(); + + // default ordering asc + actual = evaluateQuery("processes: identifier eq 'test' sort by id").getPageable(); + assert actual.getSort().isSorted(); + List orders = actual.getSort().toList(); + assert orders.size() == 1; + assert orders.get(0).getProperty().equals("id"); + assert orders.get(0).getDirection() == Sort.Direction.ASC; + + + // set ordering + actual = evaluateQuery("processes: identifier eq 'test' sort by id desc").getPageable(); + assert actual.getSort().isSorted(); + orders = actual.getSort().toList(); + assert orders.size() == 1; + assert orders.get(0).getProperty().equals("id"); + assert orders.get(0).getDirection() == Sort.Direction.DESC; + + actual = evaluateQuery("processes: identifier eq 'test' sort by identifier desc").getPageable(); + assert actual.getSort().isSorted(); + orders = actual.getSort().toList(); + assert orders.size() == 1; + assert orders.get(0).getProperty().equals("identifier"); + assert orders.get(0).getDirection() == Sort.Direction.DESC; + + actual = evaluateQuery("processes: identifier eq 'test' sort by title asc").getPageable(); + assert actual.getSort().isSorted(); + orders = actual.getSort().toList(); + assert orders.size() == 1; + assert orders.get(0).getProperty().equals("title.defaultValue"); + assert orders.get(0).getDirection() == Sort.Direction.ASC; + + actual = evaluateQuery("processes: identifier eq 'test' sort by version asc").getPageable(); + assert actual.getSort().isSorted(); + orders = actual.getSort().toList(); + assert orders.size() == 1; + assert orders.get(0).getProperty().equals("version"); + assert orders.get(0).getDirection() == Sort.Direction.ASC; + + actual = evaluateQuery("processes: identifier eq 'test' sort by creationDate asc").getPageable(); + assert actual.getSort().isSorted(); + orders = actual.getSort().toList(); + assert orders.size() == 1; + assert orders.get(0).getProperty().equals("creationDate"); + assert orders.get(0).getDirection() == Sort.Direction.ASC; + + // complex set ordering + actual = evaluateQuery("processes: identifier eq 'test' sort by id asc, title desc").getPageable(); + assert actual.getSort().isSorted(); + orders = actual.getSort().toList(); + assert orders.size() == 2; + assert orders.get(0).getProperty().equals("id"); + assert orders.get(0).getDirection().equals(Sort.Direction.ASC); + assert orders.get(1).getProperty().equals("title.defaultValue"); + assert orders.get(1).getDirection().equals(Sort.Direction.DESC); + + // complex default ordering + actual = evaluateQuery("processes: identifier eq 'test' sort by id asc, title").getPageable(); + assert actual.getSort().isSorted(); + orders = actual.getSort().toList(); + assert orders.size() == 2; + assert orders.get(0).getProperty().equals("id"); + assert orders.get(0).getDirection().equals(Sort.Direction.ASC); + assert orders.get(1).getProperty().equals("title.defaultValue"); + assert orders.get(1).getDirection().equals(Sort.Direction.ASC); + } + + @Test + public void testCaseSortingMongoDbQuery() { + // default (no sort) + Pageable actual = evaluateQuery("cases: processIdentifier eq 'test'").getPageable(); + assert !actual.getSort().isSorted(); + + // default ordering asc + actual = evaluateQuery("cases: processIdentifier eq 'test' sort by id").getPageable(); + assert actual.getSort().isSorted(); + List orders = actual.getSort().toList(); + assert orders.size() == 1; + assert orders.get(0).getProperty().equals("id"); + assert orders.get(0).getDirection() == Sort.Direction.ASC; + + + // set ordering + actual = evaluateQuery("cases: processIdentifier eq 'test' sort by id desc").getPageable(); + assert actual.getSort().isSorted(); + orders = actual.getSort().toList(); + assert orders.size() == 1; + assert orders.get(0).getProperty().equals("id"); + assert orders.get(0).getDirection() == Sort.Direction.DESC; + + actual = evaluateQuery("cases: processIdentifier eq 'test' sort by processIdentifier desc").getPageable(); + assert actual.getSort().isSorted(); + orders = actual.getSort().toList(); + assert orders.size() == 1; + assert orders.get(0).getProperty().equals("processIdentifier"); + assert orders.get(0).getDirection() == Sort.Direction.DESC; + + actual = evaluateQuery("cases: processIdentifier eq 'test' sort by title asc").getPageable(); + assert actual.getSort().isSorted(); + orders = actual.getSort().toList(); + assert orders.size() == 1; + assert orders.get(0).getProperty().equals("title"); + assert orders.get(0).getDirection() == Sort.Direction.ASC; + + actual = evaluateQuery("cases: processIdentifier eq 'test' sort by processId asc").getPageable(); + assert actual.getSort().isSorted(); + orders = actual.getSort().toList(); + assert orders.size() == 1; + assert orders.get(0).getProperty().equals("petriNetObjectId"); + assert orders.get(0).getDirection() == Sort.Direction.ASC; + + actual = evaluateQuery("cases: processIdentifier eq 'test' sort by creationDate asc").getPageable(); + assert actual.getSort().isSorted(); + orders = actual.getSort().toList(); + assert orders.size() == 1; + assert orders.get(0).getProperty().equals("creationDate"); + assert orders.get(0).getDirection() == Sort.Direction.ASC; + + + actual = evaluateQuery("cases: processIdentifier eq 'test' sort by author desc").getPageable(); + assert actual.getSort().isSorted(); + orders = actual.getSort().toList(); + assert orders.size() == 1; + assert orders.get(0).getProperty().equals("author.id"); + assert orders.get(0).getDirection() == Sort.Direction.DESC; + + // complex set ordering + actual = evaluateQuery("cases: processIdentifier eq 'test' sort by id asc, title desc").getPageable(); + assert actual.getSort().isSorted(); + orders = actual.getSort().toList(); + assert orders.size() == 2; + assert orders.get(0).getProperty().equals("id"); + assert orders.get(0).getDirection().equals(Sort.Direction.ASC); + assert orders.get(1).getProperty().equals("title"); + assert orders.get(1).getDirection().equals(Sort.Direction.DESC); + + // complex default ordering + actual = evaluateQuery("cases: processIdentifier eq 'test' sort by id asc, title").getPageable(); + assert actual.getSort().isSorted(); + orders = actual.getSort().toList(); + assert orders.size() == 2; + assert orders.get(0).getProperty().equals("id"); + assert orders.get(0).getDirection().equals(Sort.Direction.ASC); + assert orders.get(1).getProperty().equals("title"); + assert orders.get(1).getDirection().equals(Sort.Direction.ASC); + } + + @Test + public void testCaseSortingElasticQuery() { + // default (no sort) + Pageable actual = evaluateQuery("cases: data.field1.value eq 'test'").getPageable(); + assert !actual.getSort().isSorted(); + + // default ordering asc + actual = evaluateQuery("cases: data.field1.value eq 'test' sort by id").getPageable(); + assert actual.getSort().isSorted(); + List orders = actual.getSort().toList(); + assert orders.size() == 1; + assert orders.get(0).getProperty().equals("stringId.keyword"); + assert orders.get(0).getDirection() == Sort.Direction.ASC; + + + // set ordering + actual = evaluateQuery("cases: data.field1.value eq 'test' sort by id desc").getPageable(); + assert actual.getSort().isSorted(); + orders = actual.getSort().toList(); + assert orders.size() == 1; + assert orders.get(0).getProperty().equals("stringId.keyword"); + assert orders.get(0).getDirection() == Sort.Direction.DESC; + + actual = evaluateQuery("cases: data.field1.value eq 'test' sort by processIdentifier desc").getPageable(); + assert actual.getSort().isSorted(); + orders = actual.getSort().toList(); + assert orders.size() == 1; + assert orders.get(0).getProperty().equals("processIdentifier.keyword"); + assert orders.get(0).getDirection() == Sort.Direction.DESC; + + actual = evaluateQuery("cases: data.field1.value eq 'test' sort by title asc").getPageable(); + assert actual.getSort().isSorted(); + orders = actual.getSort().toList(); + assert orders.size() == 1; + assert orders.get(0).getProperty().equals("title.keyword"); + assert orders.get(0).getDirection() == Sort.Direction.ASC; + + actual = evaluateQuery("cases: data.field1.value eq 'test' sort by processId asc").getPageable(); + assert actual.getSort().isSorted(); + orders = actual.getSort().toList(); + assert orders.size() == 1; + assert orders.get(0).getProperty().equals("processId.keyword"); + assert orders.get(0).getDirection() == Sort.Direction.ASC; + + actual = evaluateQuery("cases: data.field1.value eq 'test' sort by creationDate asc").getPageable(); + assert actual.getSort().isSorted(); + orders = actual.getSort().toList(); + assert orders.size() == 1; + assert orders.get(0).getProperty().equals("creationDateSortable"); + assert orders.get(0).getDirection() == Sort.Direction.ASC; + + actual = evaluateQuery("cases: data.field1.value eq 'test' sort by author desc").getPageable(); + assert actual.getSort().isSorted(); + orders = actual.getSort().toList(); + assert orders.size() == 1; + assert orders.get(0).getProperty().equals("author.keyword"); + assert orders.get(0).getDirection() == Sort.Direction.DESC; + + actual = evaluateQuery("cases: data.field1.value eq 'test' sort by places.p1.marking desc").getPageable(); + assert actual.getSort().isSorted(); + orders = actual.getSort().toList(); + assert orders.size() == 1; + assert orders.get(0).getProperty().equals("places.p1.marking"); + assert orders.get(0).getDirection() == Sort.Direction.DESC; + + actual = evaluateQuery("cases: data.field1.value eq 'test' sort by tasks.t1.state desc").getPageable(); + assert actual.getSort().isSorted(); + orders = actual.getSort().toList(); + assert orders.size() == 1; + assert orders.get(0).getProperty().equals("tasks.t1.state.keyword"); + assert orders.get(0).getDirection() == Sort.Direction.DESC; + + actual = evaluateQuery("cases: data.field1.value eq 'test' sort by tasks.t1.userId desc").getPageable(); + assert actual.getSort().isSorted(); + orders = actual.getSort().toList(); + assert orders.size() == 1; + assert orders.get(0).getProperty().equals("tasks.t1.userId.keyword"); + assert orders.get(0).getDirection() == Sort.Direction.DESC; + + // complex set ordering + actual = evaluateQuery("cases: data.field1.value eq 'test' sort by id asc, title desc").getPageable(); + assert actual.getSort().isSorted(); + orders = actual.getSort().toList(); + assert orders.size() == 2; + assert orders.get(0).getProperty().equals("stringId.keyword"); + assert orders.get(0).getDirection().equals(Sort.Direction.ASC); + assert orders.get(1).getProperty().equals("title.keyword"); + assert orders.get(1).getDirection().equals(Sort.Direction.DESC); + + // complex default ordering + actual = evaluateQuery("cases: data.field1.value eq 'test' sort by id asc, title").getPageable(); + assert actual.getSort().isSorted(); + orders = actual.getSort().toList(); + assert orders.size() == 2; + assert orders.get(0).getProperty().equals("stringId.keyword"); + assert orders.get(0).getDirection().equals(Sort.Direction.ASC); + assert orders.get(1).getProperty().equals("title.keyword"); + assert orders.get(1).getDirection().equals(Sort.Direction.ASC); + } + + @Test + public void testTaskSortingQuery() { + // default (no sort) + Pageable actual = evaluateQuery("tasks: title eq 'test'").getPageable(); + assert !actual.getSort().isSorted(); + + // default ordering asc + actual = evaluateQuery("tasks: title eq 'test' sort by id").getPageable(); + assert actual.getSort().isSorted(); + List orders = actual.getSort().toList(); + assert orders.size() == 1; + assert orders.get(0).getProperty().equals("id"); + assert orders.get(0).getDirection() == Sort.Direction.ASC; + + + // set ordering + actual = evaluateQuery("tasks: title eq 'test' sort by id desc").getPageable(); + assert actual.getSort().isSorted(); + orders = actual.getSort().toList(); + assert orders.size() == 1; + assert orders.get(0).getProperty().equals("id"); + assert orders.get(0).getDirection() == Sort.Direction.DESC; + + actual = evaluateQuery("tasks: title eq 'test' sort by transitionId desc").getPageable(); + assert actual.getSort().isSorted(); + orders = actual.getSort().toList(); + assert orders.size() == 1; + assert orders.get(0).getProperty().equals("transitionId"); + assert orders.get(0).getDirection() == Sort.Direction.DESC; + + actual = evaluateQuery("tasks: title eq 'test' sort by title asc").getPageable(); + assert actual.getSort().isSorted(); + orders = actual.getSort().toList(); + assert orders.size() == 1; + assert orders.get(0).getProperty().equals("title.defaultValue"); + assert orders.get(0).getDirection() == Sort.Direction.ASC; + + actual = evaluateQuery("tasks: title eq 'test' sort by processId asc").getPageable(); + assert actual.getSort().isSorted(); + orders = actual.getSort().toList(); + assert orders.size() == 1; + assert orders.get(0).getProperty().equals("processId"); + assert orders.get(0).getDirection() == Sort.Direction.ASC; + + actual = evaluateQuery("tasks: title eq 'test' sort by caseId asc").getPageable(); + assert actual.getSort().isSorted(); + orders = actual.getSort().toList(); + assert orders.size() == 1; + assert orders.get(0).getProperty().equals("caseId"); + assert orders.get(0).getDirection() == Sort.Direction.ASC; + + actual = evaluateQuery("tasks: title eq 'test' sort by userId asc").getPageable(); + assert actual.getSort().isSorted(); + orders = actual.getSort().toList(); + assert orders.size() == 1; + assert orders.get(0).getProperty().equals("userId"); + assert orders.get(0).getDirection() == Sort.Direction.ASC; + + actual = evaluateQuery("tasks: title eq 'test' sort by lastAssign asc").getPageable(); + assert actual.getSort().isSorted(); + orders = actual.getSort().toList(); + assert orders.size() == 1; + assert orders.get(0).getProperty().equals("lastAssigned"); + assert orders.get(0).getDirection() == Sort.Direction.ASC; + + + actual = evaluateQuery("tasks: title eq 'test' sort by lastFinish desc").getPageable(); + assert actual.getSort().isSorted(); + orders = actual.getSort().toList(); + assert orders.size() == 1; + assert orders.get(0).getProperty().equals("lastFinished"); + assert orders.get(0).getDirection() == Sort.Direction.DESC; + + // complex set ordering + actual = evaluateQuery("tasks: title eq 'test' sort by id asc, title desc").getPageable(); + assert actual.getSort().isSorted(); + orders = actual.getSort().toList(); + assert orders.size() == 2; + assert orders.get(0).getProperty().equals("id"); + assert orders.get(0).getDirection().equals(Sort.Direction.ASC); + assert orders.get(1).getProperty().equals("title.defaultValue"); + assert orders.get(1).getDirection().equals(Sort.Direction.DESC); + + // complex default ordering + actual = evaluateQuery("tasks: title eq 'test' sort by id asc, title").getPageable(); + assert actual.getSort().isSorted(); + orders = actual.getSort().toList(); + assert orders.size() == 2; + assert orders.get(0).getProperty().equals("id"); + assert orders.get(0).getDirection().equals(Sort.Direction.ASC); + assert orders.get(1).getProperty().equals("title.defaultValue"); + assert orders.get(1).getDirection().equals(Sort.Direction.ASC); + } + + @Test + public void testUserSortingQuery() { + // default (no sort) + Pageable actual = evaluateQuery("users: name eq 'test'").getPageable(); + assert !actual.getSort().isSorted(); + + // default ordering asc + actual = evaluateQuery("users: name eq 'test' sort by id").getPageable(); + assert actual.getSort().isSorted(); + List orders = actual.getSort().toList(); + assert orders.size() == 1; + assert orders.get(0).getProperty().equals("id"); + assert orders.get(0).getDirection() == Sort.Direction.ASC; + + + // set ordering + actual = evaluateQuery("users: name eq 'test' sort by id desc").getPageable(); + assert actual.getSort().isSorted(); + orders = actual.getSort().toList(); + assert orders.size() == 1; + assert orders.get(0).getProperty().equals("id"); + assert orders.get(0).getDirection() == Sort.Direction.DESC; + + actual = evaluateQuery("users: name eq 'test' sort by name desc").getPageable(); + assert actual.getSort().isSorted(); + orders = actual.getSort().toList(); + assert orders.size() == 1; + assert orders.get(0).getProperty().equals("name"); + assert orders.get(0).getDirection() == Sort.Direction.DESC; + + actual = evaluateQuery("users: name eq 'test' sort by surname asc").getPageable(); + assert actual.getSort().isSorted(); + orders = actual.getSort().toList(); + assert orders.size() == 1; + assert orders.get(0).getProperty().equals("surname"); + assert orders.get(0).getDirection() == Sort.Direction.ASC; + + actual = evaluateQuery("users: name eq 'test' sort by email asc").getPageable(); + assert actual.getSort().isSorted(); + orders = actual.getSort().toList(); + assert orders.size() == 1; + assert orders.get(0).getProperty().equals("email"); + assert orders.get(0).getDirection() == Sort.Direction.ASC; + + // complex set ordering + actual = evaluateQuery("users: name eq 'test' sort by id asc, name desc").getPageable(); + assert actual.getSort().isSorted(); + orders = actual.getSort().toList(); + assert orders.size() == 2; + assert orders.get(0).getProperty().equals("id"); + assert orders.get(0).getDirection().equals(Sort.Direction.ASC); + assert orders.get(1).getProperty().equals("name"); + assert orders.get(1).getDirection().equals(Sort.Direction.DESC); + + // complex default ordering + actual = evaluateQuery("users: name eq 'test' sort by id asc, name").getPageable(); + assert actual.getSort().isSorted(); + orders = actual.getSort().toList(); + assert orders.size() == 2; + assert orders.get(0).getProperty().equals("id"); + assert orders.get(0).getDirection().equals(Sort.Direction.ASC); + assert orders.get(1).getProperty().equals("name"); + assert orders.get(1).getDirection().equals(Sort.Direction.ASC); + } + + @Test + public void testProcessQueriesFail() { + // using case, task, user attributes + Assertions.assertThrows(IllegalArgumentException.class, () -> evaluateQuery("process: processId eq 'test'")); + Assertions.assertThrows(IllegalArgumentException.class, () -> evaluateQuery("process: processIdentifier eq 'test'")); + Assertions.assertThrows(IllegalArgumentException.class, () -> evaluateQuery("process: author eq 'test'")); + Assertions.assertThrows(IllegalArgumentException.class, () -> evaluateQuery("process: places.p1.marking eq 1")); + Assertions.assertThrows(IllegalArgumentException.class, () -> evaluateQuery("process: tasks.t1.state eq enabled")); + Assertions.assertThrows(IllegalArgumentException.class, () -> evaluateQuery("process: tasks.t1.userId eq 'test'")); + Assertions.assertThrows(IllegalArgumentException.class, () -> evaluateQuery("process: data.field1.value eq 'test'")); + Assertions.assertThrows(IllegalArgumentException.class, () -> evaluateQuery("process: data.field1.options contains 'test'")); + Assertions.assertThrows(IllegalArgumentException.class, () -> evaluateQuery("process: transitionId eq 'test'")); + Assertions.assertThrows(IllegalArgumentException.class, () -> evaluateQuery("process: state eq enabled")); + Assertions.assertThrows(IllegalArgumentException.class, () -> evaluateQuery("process: userId eq 'test'")); + Assertions.assertThrows(IllegalArgumentException.class, () -> evaluateQuery("process: caseId eq 'test'")); + Assertions.assertThrows(IllegalArgumentException.class, () -> evaluateQuery("process: lastAssign eq 2020-03-03")); + Assertions.assertThrows(IllegalArgumentException.class, () -> evaluateQuery("process: lastFinish eq 2020-03-03")); + Assertions.assertThrows(IllegalArgumentException.class, () -> evaluateQuery("process: name eq 'test'")); + Assertions.assertThrows(IllegalArgumentException.class, () -> evaluateQuery("process: surname eq 'test'")); + Assertions.assertThrows(IllegalArgumentException.class, () -> evaluateQuery("process: email eq 'test'")); + } + + @Test + public void testCaseQueriesFail() { + // using process, task, user attributes + Assertions.assertThrows(IllegalArgumentException.class, () -> evaluateQuery("case: identifier eq 'test'")); + Assertions.assertThrows(IllegalArgumentException.class, () -> evaluateQuery("case: version eq 1.1.1")); + Assertions.assertThrows(IllegalArgumentException.class, () -> evaluateQuery("case: transitionId eq 1")); + Assertions.assertThrows(IllegalArgumentException.class, () -> evaluateQuery("case: state eq enabled")); + Assertions.assertThrows(IllegalArgumentException.class, () -> evaluateQuery("case: userId eq 'test'")); + Assertions.assertThrows(IllegalArgumentException.class, () -> evaluateQuery("case: caseId eq 'test'")); + Assertions.assertThrows(IllegalArgumentException.class, () -> evaluateQuery("case: lastAssign eq 2020-03-03")); + Assertions.assertThrows(IllegalArgumentException.class, () -> evaluateQuery("case: lastFinish eq 2020-03-03")); + Assertions.assertThrows(IllegalArgumentException.class, () -> evaluateQuery("case: name eq 'test'")); + Assertions.assertThrows(IllegalArgumentException.class, () -> evaluateQuery("case: surname eq 'test'")); + Assertions.assertThrows(IllegalArgumentException.class, () -> evaluateQuery("case: email eq 'test'")); + } + + @Test + public void testTaskQueriesFail() { + // using process, case, user attributes + Assertions.assertThrows(IllegalArgumentException.class, () -> evaluateQuery("task: identifier eq 'test'")); + Assertions.assertThrows(IllegalArgumentException.class, () -> evaluateQuery("task: version eq 1.1.1")); + Assertions.assertThrows(IllegalArgumentException.class, () -> evaluateQuery("task: creationDate eq 2020-03-03")); + Assertions.assertThrows(IllegalArgumentException.class, () -> evaluateQuery("task: processIdentifier eq 'test'")); + Assertions.assertThrows(IllegalArgumentException.class, () -> evaluateQuery("task: places.p1.marking eq 1")); + Assertions.assertThrows(IllegalArgumentException.class, () -> evaluateQuery("task: tasks.t1.state eq enabled")); + Assertions.assertThrows(IllegalArgumentException.class, () -> evaluateQuery("task: tasks.t1.userId eq 'test'")); + Assertions.assertThrows(IllegalArgumentException.class, () -> evaluateQuery("task: data.field1.value eq 'test'")); + Assertions.assertThrows(IllegalArgumentException.class, () -> evaluateQuery("task: data.field1.options contains 'test'")); + Assertions.assertThrows(IllegalArgumentException.class, () -> evaluateQuery("task: name eq 'test'")); + Assertions.assertThrows(IllegalArgumentException.class, () -> evaluateQuery("task: surname eq 'test'")); + Assertions.assertThrows(IllegalArgumentException.class, () -> evaluateQuery("task: email eq 'test'")); + } + + @Test + public void testUserQueriesFail() { + // using process, case, task attributes + Assertions.assertThrows(IllegalArgumentException.class, () -> evaluateQuery("user: identifier eq 'test'")); + Assertions.assertThrows(IllegalArgumentException.class, () -> evaluateQuery("user: version eq 1.1.1")); + Assertions.assertThrows(IllegalArgumentException.class, () -> evaluateQuery("user: creationDate eq 2020-03-03")); + Assertions.assertThrows(IllegalArgumentException.class, () -> evaluateQuery("user: processId eq 'test'")); + Assertions.assertThrows(IllegalArgumentException.class, () -> evaluateQuery("user: processIdentifier eq 'test'")); + Assertions.assertThrows(IllegalArgumentException.class, () -> evaluateQuery("user: author eq 'test'")); + Assertions.assertThrows(IllegalArgumentException.class, () -> evaluateQuery("user: places.p1.marking eq 1")); + Assertions.assertThrows(IllegalArgumentException.class, () -> evaluateQuery("user: tasks.t1.state eq enabled")); + Assertions.assertThrows(IllegalArgumentException.class, () -> evaluateQuery("user: tasks.t1.userId eq 'test'")); + Assertions.assertThrows(IllegalArgumentException.class, () -> evaluateQuery("user: data.field1.value eq 'test'")); + Assertions.assertThrows(IllegalArgumentException.class, () -> evaluateQuery("user: data.field1.options contains 'test'")); + Assertions.assertThrows(IllegalArgumentException.class, () -> evaluateQuery("user: transitionId eq 'test'")); + Assertions.assertThrows(IllegalArgumentException.class, () -> evaluateQuery("user: state eq enabled")); + Assertions.assertThrows(IllegalArgumentException.class, () -> evaluateQuery("user: userId eq 'test'")); + Assertions.assertThrows(IllegalArgumentException.class, () -> evaluateQuery("user: caseId eq 'test'")); + Assertions.assertThrows(IllegalArgumentException.class, () -> evaluateQuery("user: lastAssign eq 2020-03-03")); + Assertions.assertThrows(IllegalArgumentException.class, () -> evaluateQuery("user: lastFinish eq 2020-03-03")); + } + + @Test + public void testComparisonTypeFail() { + // id comparison + Assertions.assertThrows(IllegalArgumentException.class, () -> evaluateQuery("case: id contains 'test'")); + Assertions.assertThrows(IllegalArgumentException.class, () -> evaluateQuery("case: id lt 'test'")); + Assertions.assertThrows(IllegalArgumentException.class, () -> evaluateQuery("case: id lte 'test'")); + Assertions.assertThrows(IllegalArgumentException.class, () -> evaluateQuery("case: id gt 'test'")); + Assertions.assertThrows(IllegalArgumentException.class, () -> evaluateQuery("case: id gte 'test'")); + + // number comparison + Assertions.assertThrows(IllegalArgumentException.class, () -> evaluateQuery("case: places.p1.marking contains 1")); + + // date/datetime comparison + Assertions.assertThrows(IllegalArgumentException.class, () -> evaluateQuery("case: creationDate contains 2020-03-03")); + + // boolean comparison + Assertions.assertThrows(IllegalArgumentException.class, () -> evaluateQuery("case: data.field1.value contains true")); + Assertions.assertThrows(IllegalArgumentException.class, () -> evaluateQuery("case: data.field1.value lt true")); + Assertions.assertThrows(IllegalArgumentException.class, () -> evaluateQuery("case: data.field1.value lte true")); + Assertions.assertThrows(IllegalArgumentException.class, () -> evaluateQuery("case: data.field1.value gt true")); + Assertions.assertThrows(IllegalArgumentException.class, () -> evaluateQuery("case: data.field1.value gte true")); + } + + private static void checkStringComparison(MongoDbUtils mongoDbUtils, String resource, String attribute, StringPath stringPath) { + Predicate actual = evaluateQuery(String.format("%s: %s eq 'test'", resource, attribute)).getFullMongoQuery(); + Predicate expected = stringPath.eq("test"); + + compareMongoQueries(mongoDbUtils, actual, expected); + + actual = evaluateQuery(String.format("%s: %s contains 'test'", resource, attribute)).getFullMongoQuery(); + expected = stringPath.contains("test"); + + compareMongoQueries(mongoDbUtils, actual, expected); + + actual = evaluateQuery(String.format("%s: %s lt 'test'", resource, attribute)).getFullMongoQuery(); + expected = stringPath.lt("test"); + + compareMongoQueries(mongoDbUtils, actual, expected); + + actual = evaluateQuery(String.format("%s: %s lte 'test'", resource, attribute)).getFullMongoQuery(); + expected = stringPath.loe("test"); + + compareMongoQueries(mongoDbUtils, actual, expected); + + actual = evaluateQuery(String.format("%s: %s gt 'test'", resource, attribute)).getFullMongoQuery(); + expected = stringPath.gt("test"); + + compareMongoQueries(mongoDbUtils, actual, expected); + + actual = evaluateQuery(String.format("%s: %s gte 'test'", resource, attribute)).getFullMongoQuery(); + expected = stringPath.goe("test"); + + compareMongoQueries(mongoDbUtils, actual, expected); + + actual = evaluateQuery(String.format("%s: %s in ('test1', 'test2', 'test3')", resource, attribute)).getFullMongoQuery(); + expected = stringPath.in(List.of("test1", "test2", "test3")); + + compareMongoQueries(mongoDbUtils, actual, expected); + + actual = evaluateQuery(String.format("%s: %s not in ('test1', 'test2', 'test3')", resource, attribute)).getFullMongoQuery(); + expected = stringPath.in(List.of("test1", "test2", "test3")).not(); + + compareMongoQueries(mongoDbUtils, actual, expected); + + actual = evaluateQuery(String.format("%s: %s in ('test1' : 'test2')", resource, attribute)).getFullMongoQuery(); + expected = stringPath.gt("test1").and(stringPath.lt("test2")); + + compareMongoQueries(mongoDbUtils, actual, expected); + + actual = evaluateQuery(String.format("%s: %s in ['test1' : 'test2']", resource, attribute)).getFullMongoQuery(); + expected = stringPath.goe("test1").and(stringPath.loe("test2")); + + compareMongoQueries(mongoDbUtils, actual, expected); + + actual = evaluateQuery(String.format("%s: %s not in ('test1' : 'test2']", resource, attribute)).getFullMongoQuery(); + expected = stringPath.gt("test1").and(stringPath.loe("test2")).not(); + + compareMongoQueries(mongoDbUtils, actual, expected); + } + + private static void checkDateComparison(MongoDbUtils mongoDbUtils, String resource, String attribute, DateTimePath dateTimePath) { + LocalDateTime date1 = LocalDateTime.of(2011, 12, 3, 10, 15, 30); + LocalDateTime date2 = LocalDateTime.of(2011, 12, 3, 11, 15, 30); + LocalDateTime date3 = LocalDateTime.of(2011, 12, 3, 12, 15, 30); + LocalDateTime date4 = LocalDateTime.of(2011, 12, 3, 12, 0, 0); + LocalDateTime date5 = LocalDateTime.of(2011, 12, 3, 12, 0, 0); + LocalDateTime date6 = LocalDateTime.of(2011, 12, 3, 12, 0, 0); + + Predicate actual = evaluateQuery(String.format("%s: %s eq 2011-12-03T10:15:30", resource, attribute)).getFullMongoQuery(); + Predicate expected = dateTimePath.eq(LocalDateTime.of(2011, 12, 3, 10, 15, 30)); + + compareMongoQueries(mongoDbUtils, actual, expected); + + actual = evaluateQuery(String.format("%s: %s lt 2011-12-03T10:15:30", resource, attribute)).getFullMongoQuery(); + expected = dateTimePath.lt(LocalDateTime.of(2011, 12, 3, 10, 15, 30)); + + compareMongoQueries(mongoDbUtils, actual, expected); + + actual = evaluateQuery(String.format("%s: %s lte 2011-12-03T10:15:30", resource, attribute)).getFullMongoQuery(); + expected = dateTimePath.loe(LocalDateTime.of(2011, 12, 3, 10, 15, 30)); + + compareMongoQueries(mongoDbUtils, actual, expected); + + actual = evaluateQuery(String.format("%s: %s gt 2011-12-03T10:15:30", resource, attribute)).getFullMongoQuery(); + expected = dateTimePath.gt(LocalDateTime.of(2011, 12, 3, 10, 15, 30)); + + compareMongoQueries(mongoDbUtils, actual, expected); + + actual = evaluateQuery(String.format("%s: %s gte 2011-12-03T10:15:30", resource, attribute)).getFullMongoQuery(); + expected = dateTimePath.goe(LocalDateTime.of(2011, 12, 3, 10, 15, 30)); + + compareMongoQueries(mongoDbUtils, actual, expected); + + actual = evaluateQuery(String.format("%s: %s in (2011-12-03T10:15:30, 2011-12-03T11:15:30, 2011-12-03T12:15:30)", resource, attribute)).getFullMongoQuery(); + expected = dateTimePath.in(List.of(date1, date2, date3)); + + compareMongoQueries(mongoDbUtils, actual, expected); + + actual = evaluateQuery(String.format("%s: %s not in (2011-12-03T10:15:30, 2011-12-03T11:15:30, 2011-12-03T12:15:30)", resource, attribute)).getFullMongoQuery(); + expected = dateTimePath.in(List.of(date1, date2, date3)).not(); + + compareMongoQueries(mongoDbUtils, actual, expected); + + actual = evaluateQuery(String.format("%s: %s in (2011-12-03T10:15:30 : 2011-12-03T11:15:30)", resource, attribute)).getFullMongoQuery(); + expected = dateTimePath.gt(date1).and(dateTimePath.lt(date2)); + + compareMongoQueries(mongoDbUtils, actual, expected); + + actual = evaluateQuery(String.format("%s: %s in [2011-12-03T10:15:30 : 2011-12-03T11:15:30]", resource, attribute)).getFullMongoQuery(); + expected = dateTimePath.goe(date1).and(dateTimePath.loe(date2)); + + compareMongoQueries(mongoDbUtils, actual, expected); + + actual = evaluateQuery(String.format("%s: %s not in (2011-12-03T10:15:30 : 2011-12-03T11:15:30]", resource, attribute)).getFullMongoQuery(); + expected = dateTimePath.gt(date1).and(dateTimePath.loe(date2)).not(); + + compareMongoQueries(mongoDbUtils, actual, expected); + + actual = evaluateQuery(String.format("%s: %s in (2011-12-03, 2011-12-03, 2011-12-03)", resource, attribute)).getFullMongoQuery(); + expected = dateTimePath.in(List.of(date4, date5, date6)); + + compareMongoQueries(mongoDbUtils, actual, expected); + + actual = evaluateQuery(String.format("%s: %s not in (2011-12-03, 2011-12-03, 2011-12-03)", resource, attribute)).getFullMongoQuery(); + expected = dateTimePath.in(List.of(date4, date5, date6)).not(); + + compareMongoQueries(mongoDbUtils, actual, expected); + + actual = evaluateQuery(String.format("%s: %s in (2011-12-03 : 2011-12-03)", resource, attribute)).getFullMongoQuery(); + expected = dateTimePath.gt(date4).and(dateTimePath.lt(date5)); + + compareMongoQueries(mongoDbUtils, actual, expected); + + actual = evaluateQuery(String.format("%s: %s in [2011-12-03 : 2011-12-03]", resource, attribute)).getFullMongoQuery(); + expected = dateTimePath.goe(date4).and(dateTimePath.loe(date5)); + + compareMongoQueries(mongoDbUtils, actual, expected); + + actual = evaluateQuery(String.format("%s: %s not in (2011-12-03 : 2011-12-03]", resource, attribute)).getFullMongoQuery(); + expected = dateTimePath.gt(date4).and(dateTimePath.loe(date5)).not(); + + compareMongoQueries(mongoDbUtils, actual, expected); + } + + private static void checkStringComparisonElastic(String resource, String attribute, String resultAttribute) { + String actual = evaluateQuery(String.format("%s: %s eq 'test'", resource, attribute)).getFullElasticQuery(); + String expected = String.format("%s:test", resultAttribute); + + assert actual.equals(expected); + + actual = evaluateQuery(String.format("%s: %s contains 'test'", resource, attribute)).getFullElasticQuery(); + expected = String.format("%s:*test*", resultAttribute); + + assert actual.equals(expected); + + actual = evaluateQuery(String.format("%s: %s lt 'test'", resource, attribute)).getFullElasticQuery(); + expected = String.format("%s:test", resultAttribute); + + assert actual.equals(expected); + + actual = evaluateQuery(String.format("%s: %s gte 'test'", resource, attribute)).getFullElasticQuery(); + expected = String.format("%s:>=test", resultAttribute); + + assert actual.equals(expected); + + actual = evaluateQuery(String.format("%s: %s in ('test1', 'test2', 'test3')", resource, attribute)).getFullElasticQuery(); + expected = String.format("%s:(test1 OR test2 OR test3)", resultAttribute); + + assert actual.equals(expected); + + actual = evaluateQuery(String.format("%s: %s not in ('test1', 'test2', 'test3')", resource, attribute)).getFullElasticQuery(); + expected = String.format("NOT %s:(test1 OR test2 OR test3)", resultAttribute); + + assert actual.equals(expected); + + actual = evaluateQuery(String.format("%s: %s in ('test1' : 'test2')", resource, attribute)).getFullElasticQuery(); + expected = String.format("(%s:>test1 AND %s:=test1 AND %s:<=test2)", resultAttribute, resultAttribute); + + assert actual.equals(expected); + + actual = evaluateQuery(String.format("%s: %s not in ('test1' : 'test2']", resource, attribute)).getFullElasticQuery(); + expected = String.format("NOT (%s:>test1 AND %s:<=test2)", resultAttribute, resultAttribute); + + assert actual.equals(expected); + } + + private static void checkNumberComparisonElastic(String resource, String attribute, String resultAttribute) { + String actual = evaluateQuery(String.format("%s: %s eq 1", resource, attribute)).getFullElasticQuery(); + String expected = String.format("%s:1", resultAttribute); + + assert actual.equals(expected); + + actual = evaluateQuery(String.format("%s: %s lt 1", resource, attribute)).getFullElasticQuery(); + expected = String.format("%s:<1", resultAttribute); + + assert actual.equals(expected); + + actual = evaluateQuery(String.format("%s: %s lte 1", resource, attribute)).getFullElasticQuery(); + expected = String.format("%s:<=1", resultAttribute); + + assert actual.equals(expected); + + actual = evaluateQuery(String.format("%s: %s gt 1", resource, attribute)).getFullElasticQuery(); + expected = String.format("%s:>1", resultAttribute); + + assert actual.equals(expected); + + actual = evaluateQuery(String.format("%s: %s gte 1", resource, attribute)).getFullElasticQuery(); + expected = String.format("%s:>=1", resultAttribute); + + assert actual.equals(expected); + + actual = evaluateQuery(String.format("%s: %s in (1, 2, 3)", resource, attribute)).getFullElasticQuery(); + expected = String.format("%s:(1 OR 2 OR 3)", resultAttribute); + + assert actual.equals(expected); + + actual = evaluateQuery(String.format("%s: %s not in (1, 2, 3)", resource, attribute)).getFullElasticQuery(); + expected = String.format("NOT %s:(1 OR 2 OR 3)", resultAttribute); + + assert actual.equals(expected); + + actual = evaluateQuery(String.format("%s: %s in (1 : 2)", resource, attribute)).getFullElasticQuery(); + expected = String.format("(%s:>1 AND %s:<2)", resultAttribute, resultAttribute); + + assert actual.equals(expected); + + actual = evaluateQuery(String.format("%s: %s in [1 : 2]", resource, attribute)).getFullElasticQuery(); + expected = String.format("(%s:>=1 AND %s:<=2)", resultAttribute, resultAttribute); + + assert actual.equals(expected); + + actual = evaluateQuery(String.format("%s: %s not in (1 : 2]", resource, attribute)).getFullElasticQuery(); + expected = String.format("NOT (%s:>1 AND %s:<=2)", resultAttribute, resultAttribute); + + assert actual.equals(expected); + } + + private static void checkDateComparisonElastic(String resource, String attribute, String resultAttribute) { + LocalDateTime date1 = LocalDateTime.of(2011, 12, 3, 10, 15, 30); + LocalDateTime date2 = LocalDateTime.of(2011, 12, 3, 11, 15, 30); + LocalDateTime date3 = LocalDateTime.of(2011, 12, 3, 12, 15, 30); + LocalDateTime date4 = LocalDateTime.of(2011, 12, 3, 12, 0, 0); + LocalDateTime date5 = LocalDateTime.of(2011, 12, 3, 12, 0, 0); + LocalDateTime date6 = LocalDateTime.of(2011, 12, 3, 12, 0, 0); + + String actual = evaluateQuery(String.format("%s: %s eq 2011-12-03T10:15:30", resource, attribute)).getFullElasticQuery(); + String expected = String.format("%s:%s", resultAttribute, Timestamp.valueOf(date1).getTime()); + + assert actual.equals(expected); + + actual = evaluateQuery(String.format("%s: %s lt 2011-12-03T10:15:30", resource, attribute)).getFullElasticQuery(); + expected = String.format("%s:<%s", resultAttribute, Timestamp.valueOf(date1).getTime()); + + assert actual.equals(expected); + + actual = evaluateQuery(String.format("%s: %s lte 2011-12-03T10:15:30", resource, attribute)).getFullElasticQuery(); + expected = String.format("%s:<=%s", resultAttribute, Timestamp.valueOf(date1).getTime()); + + assert actual.equals(expected); + + actual = evaluateQuery(String.format("%s: %s gt 2011-12-03T10:15:30", resource, attribute)).getFullElasticQuery(); + expected = String.format("%s:>%s", resultAttribute, Timestamp.valueOf(date1).getTime()); + + assert actual.equals(expected); + + actual = evaluateQuery(String.format("%s: %s gte 2011-12-03T10:15:30", resource, attribute)).getFullElasticQuery(); + expected = String.format("%s:>=%s", resultAttribute, Timestamp.valueOf(date1).getTime()); + + assert actual.equals(expected); + + actual = evaluateQuery(String.format("%s: %s in (2011-12-03T10:15:30, 2011-12-03T11:15:30, 2011-12-03T12:15:30)", resource, attribute)).getFullElasticQuery(); + expected = String.format("%s:(%s OR %s OR %s)", resultAttribute, Timestamp.valueOf(date1).getTime(), Timestamp.valueOf(date2).getTime(), Timestamp.valueOf(date3).getTime()); + + assert actual.equals(expected); + + actual = evaluateQuery(String.format("%s: %s not in (2011-12-03T10:15:30, 2011-12-03T11:15:30, 2011-12-03T12:15:30)", resource, attribute)).getFullElasticQuery(); + expected = String.format("NOT %s:(%s OR %s OR %s)", resultAttribute, Timestamp.valueOf(date1).getTime(), Timestamp.valueOf(date2).getTime(), Timestamp.valueOf(date3).getTime()); + + assert actual.equals(expected); + + actual = evaluateQuery(String.format("%s: %s in (2011-12-03T10:15:30 : 2011-12-03T11:15:30)", resource, attribute)).getFullElasticQuery(); + expected = String.format("(%s:>%s AND %s:<%s)", resultAttribute, Timestamp.valueOf(date1).getTime(), resultAttribute, Timestamp.valueOf(date2).getTime()); + + assert actual.equals(expected); + + actual = evaluateQuery(String.format("%s: %s in [2011-12-03T10:15:30 : 2011-12-03T11:15:30]", resource, attribute)).getFullElasticQuery(); + expected = String.format("(%s:>=%s AND %s:<=%s)", resultAttribute, Timestamp.valueOf(date1).getTime(), resultAttribute, Timestamp.valueOf(date2).getTime()); + + assert actual.equals(expected); + + actual = evaluateQuery(String.format("%s: %s not in (2011-12-03T10:15:30 : 2011-12-03T11:15:30]", resource, attribute)).getFullElasticQuery(); + expected = String.format("NOT (%s:>%s AND %s:<=%s)", resultAttribute, Timestamp.valueOf(date1).getTime(), resultAttribute, Timestamp.valueOf(date2).getTime()); + + assert actual.equals(expected); + + actual = evaluateQuery(String.format("%s: %s in (2011-12-03, 2011-12-03, 2011-12-03)", resource, attribute)).getFullElasticQuery(); + expected = String.format("%s:(%s OR %s OR %s)", resultAttribute, Timestamp.valueOf(date4).getTime(), Timestamp.valueOf(date5).getTime(), Timestamp.valueOf(date6).getTime()); + + assert actual.equals(expected); + + actual = evaluateQuery(String.format("%s: %s not in (2011-12-03, 2011-12-03, 2011-12-03)", resource, attribute)).getFullElasticQuery(); + expected = String.format("NOT %s:(%s OR %s OR %s)", resultAttribute, Timestamp.valueOf(date4).getTime(), Timestamp.valueOf(date5).getTime(), Timestamp.valueOf(date6).getTime()); + + assert actual.equals(expected); + + actual = evaluateQuery(String.format("%s: %s in (2011-12-03 : 2011-12-03)", resource, attribute)).getFullElasticQuery(); + expected = String.format("(%s:>%s AND %s:<%s)", resultAttribute, Timestamp.valueOf(date4).getTime(), resultAttribute, Timestamp.valueOf(date5).getTime()); + + assert actual.equals(expected); + + actual = evaluateQuery(String.format("%s: %s in [2011-12-03 : 2011-12-03]", resource, attribute)).getFullElasticQuery(); + expected = String.format("(%s:>=%s AND %s:<=%s)", resultAttribute, Timestamp.valueOf(date4).getTime(), resultAttribute, Timestamp.valueOf(date5).getTime()); + + assert actual.equals(expected); + + actual = evaluateQuery(String.format("%s: %s not in (2011-12-03 : 2011-12-03]", resource, attribute)).getFullElasticQuery(); + expected = String.format("NOT (%s:>%s AND %s:<=%s)", resultAttribute, Timestamp.valueOf(date4).getTime(), resultAttribute, Timestamp.valueOf(date5).getTime()); + + assert actual.equals(expected); + } + + private static void compareMongoQueries(MongoDbUtils mongoDbUtils, Predicate actual, Predicate expected) { + Document actualDocument = mongoDbUtils.convertPredicateToDocument(actual); + Document expectedDocument = mongoDbUtils.convertPredicateToDocument(expected); + + assert actualDocument.equals(expectedDocument); + } +} diff --git a/src/test/java/com/netgrif/application/engine/search/SearchCaseTest.java b/src/test/java/com/netgrif/application/engine/search/SearchCaseTest.java new file mode 100644 index 00000000000..00f7b23399d --- /dev/null +++ b/src/test/java/com/netgrif/application/engine/search/SearchCaseTest.java @@ -0,0 +1,578 @@ +package com.netgrif.application.engine.search; + +import com.netgrif.application.engine.TestHelper; +import com.netgrif.application.engine.auth.domain.Authority; +import com.netgrif.application.engine.auth.domain.IUser; +import com.netgrif.application.engine.auth.domain.User; +import com.netgrif.application.engine.petrinet.domain.I18nString; +import com.netgrif.application.engine.petrinet.domain.PetriNet; +import com.netgrif.application.engine.petrinet.domain.dataset.*; +import com.netgrif.application.engine.petrinet.domain.roles.ProcessRole; +import com.netgrif.application.engine.search.interfaces.ISearchService; +import com.netgrif.application.engine.search.utils.SearchUtils; +import com.netgrif.application.engine.startup.ImportHelper; +import com.netgrif.application.engine.workflow.domain.Case; +import com.netgrif.application.engine.workflow.web.responsebodies.DataSet; +import lombok.extern.slf4j.Slf4j; +import org.junit.jupiter.api.BeforeEach; +import org.junit.jupiter.api.Test; +import org.junit.jupiter.api.extension.ExtendWith; +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.boot.test.context.SpringBootTest; +import org.springframework.test.context.ActiveProfiles; +import org.springframework.test.context.junit.jupiter.SpringExtension; + +import java.time.LocalDate; +import java.time.LocalDateTime; +import java.util.ArrayList; +import java.util.List; +import java.util.Map; +import java.util.Set; + +import static com.netgrif.application.engine.search.utils.SearchTestUtils.*; +import static com.netgrif.application.engine.search.utils.SearchUtils.toDateString; +import static com.netgrif.application.engine.search.utils.SearchUtils.toDateTimeString; + +@Slf4j +@SpringBootTest +@ActiveProfiles({"test"}) +@ExtendWith(SpringExtension.class) +public class SearchCaseTest { + + public static final String TEST_TRANSITION_ID = "search_test_t1"; + + @Autowired + private ImportHelper importHelper; + + @Autowired + private TestHelper testHelper; + + @Autowired + private ISearchService searchService; + + private Map auths; + + @BeforeEach + void setup() { + testHelper.truncateDbs(); + auths = importHelper.createAuthorities(Map.of("user", Authority.user, "admin", Authority.admin)); + } + + private PetriNet importPetriNet(String fileName) { + PetriNet testNet = importHelper.createNet(fileName).orElse(null); + assert testNet != null; + return testNet; + } + + private IUser createUser(String name, String surname, String email) { + User user = new User(email, "password", name, surname); + Authority[] authorities = new Authority[]{auths.get("user")}; + ProcessRole[] processRoles = new ProcessRole[]{}; + return importHelper.createUser(user, authorities, processRoles); + } + + private void searchAndCompare(String query, Case expectedResult) { + long count = searchService.count(query); + assert count == 1; + + Object actual = searchService.search(query); + compareById(convertToObject(actual, Case.class), expectedResult, Case::getStringId); + } + + private void searchAndCompare(String query, List expected) { + long count = searchService.count(query); + assert count == expected.size(); + + Object actual = searchService.search(query); + compareById(convertToObject(actual, Case.class), expected, Case::getStringId); + } + + private void searchAndCompareAsList(String query, List expected) { + long count = searchService.count(query); + assert count == expected.size(); + + Object actual = searchService.search(query); + compareById(convertToObjectList(actual, Case.class), expected, Case::getStringId); + } + + private void searchAndCompareAsListInOrder(String query, List expected) { + long count = searchService.count(query); + assert count == expected.size(); + + Object actual = searchService.search(query); + compareByIdInOrder(convertToObjectList(actual, Case.class), expected, Case::getStringId); + } + + @Test + public void testSearchById() { + PetriNet net = importPetriNet("search/search_test.xml"); + Case case1 = importHelper.createCase("Search Test", net); + Case case2 = importHelper.createCase("Search Test2", net); + + String query = String.format("case: id eq '%s'", case1.getStringId()); + + searchAndCompare(query, case1); + + // in list + String queryInList = String.format("cases: id in ('%s', '%s')", case1.getStringId(), case2.getStringId()); + + searchAndCompareAsList(queryInList, List.of(case1, case2)); + + // sort + String querySort = String.format("cases: processIdentifier eq '%s' sort by id", net.getIdentifier()); + String querySort2 = String.format("cases: processIdentifier eq '%s' sort by id desc", net.getIdentifier()); + + searchAndCompareAsListInOrder(querySort, List.of(case1, case2)); + searchAndCompareAsListInOrder(querySort2, List.of(case2, case1)); + } + + @Test + public void testSearchByProcessId() { + PetriNet net = importPetriNet("search/search_test.xml"); + PetriNet net2 = importPetriNet("search/search_test2.xml"); + Case case1 = importHelper.createCase("Search Test", net); + Case case2 = importHelper.createCase("Search Test", net); + Case case3 = importHelper.createCase("Search Test2", net2); + + String queryEq = String.format("case: processId eq '%s'", net.getStringId()); + String queryOther = String.format("case: processId eq '%s'", net2.getStringId()); + String queryMore = String.format("cases: processId eq '%s'", net.getStringId()); + + searchAndCompare(queryEq, List.of(case1, case2)); + searchAndCompare(queryOther, case3); + searchAndCompareAsList(queryMore, List.of(case1, case2)); + + // in list + String queryInList = String.format("cases: processId in ('%s', '%s')", net.getStringId(), net2.getStringId()); + + searchAndCompareAsList(queryInList, List.of(case1, case2, case3)); + + // sort + String querySort = String.format("cases: processIdentifier in ('%s', '%s') sort by processId", net.getIdentifier(), net2.getIdentifier()); + String querySort2 = String.format("cases: processIdentifier in ('%s', '%s') sort by processId desc", net.getIdentifier(), net2.getIdentifier()); + + searchAndCompareAsListInOrder(querySort, List.of(case1, case2, case3)); + searchAndCompareAsListInOrder(querySort2, List.of(case3, case1, case2)); + } + + @Test + public void testSearchByProcessIdentifier() { + PetriNet net = importPetriNet("search/search_test.xml"); + PetriNet net2 = importPetriNet("search/search_test2.xml"); + PetriNet net3 = importPetriNet("search/search_test3.xml"); + Case case1 = importHelper.createCase("Search Test", net); + Case case2 = importHelper.createCase("Search Test", net); + Case case3 = importHelper.createCase("Search Test2", net2); + Case case4 = importHelper.createCase("Search Test3", net3); + + String query = String.format("case: processIdentifier eq '%s'", net.getIdentifier()); + String queryOther = String.format("case: processIdentifier eq '%s'", net2.getIdentifier()); + String queryMore = String.format("cases: processIdentifier eq '%s'", net.getIdentifier()); + + searchAndCompare(query, List.of(case1, case2)); + searchAndCompare(queryOther, case3); + searchAndCompareAsList(queryMore, List.of(case1, case2)); + + // in list + String queryInList = String.format("cases: processIdentifier in ('%s', '%s', '%s')", net.getIdentifier(), net2.getIdentifier(), net3.getIdentifier()); + searchAndCompareAsList(queryInList, List.of(case1, case2, case3, case4)); + + // in range + String queryInRange = String.format("cases: processIdentifier in ['%s' : '%s')", net.getIdentifier(), net3.getIdentifier()); + searchAndCompareAsList(queryInRange, List.of(case1, case2, case3)); + + // sort + String querySort = String.format("cases: processIdentifier in ('%s', '%s') sort by processIdentifier", net.getIdentifier(), net2.getIdentifier()); + String querySort2 = String.format("cases: processIdentifier in ('%s', '%s') sort by processIdentifier desc", net.getIdentifier(), net2.getIdentifier()); + + searchAndCompareAsListInOrder(querySort, List.of(case1, case2, case3)); + searchAndCompareAsListInOrder(querySort2, List.of(case3, case1, case2)); + } + + @Test + public void testSearchByTitle() { + PetriNet net = importPetriNet("search/search_test.xml"); + Case case1 = importHelper.createCase("Search Test", net); + Case case2 = importHelper.createCase("Search Test", net); + Case case3 = importHelper.createCase("Search Test2", net); + + String query = String.format("case: title eq '%s'", case1.getTitle()); + String queryOther = String.format("case: title eq '%s'", case3.getTitle()); + String queryMore = String.format("cases: title eq '%s'", case1.getTitle()); + + searchAndCompare(query, List.of(case1, case2)); + searchAndCompare(queryOther, case3); + searchAndCompareAsList(queryMore, List.of(case1, case2)); + + // in list + String queryInList = String.format("cases: processIdentifier eq '%s' and title in ('%s', '%s')", net.getIdentifier(), case1.getTitle(), case3.getTitle()); + searchAndCompareAsList(queryInList, List.of(case1, case2, case3)); + + // in range + String queryInRange = String.format("cases: processIdentifier eq '%s' and title in ['%s' : '%s')", net.getIdentifier(), case1.getTitle(), case3.getTitle()); + searchAndCompareAsList(queryInRange, List.of(case1, case2)); + + // sort + String querySort = String.format("cases: processIdentifier eq '%s' sort by title", net.getIdentifier()); + String querySort2 = String.format("cases: processIdentifier eq '%s' sort by title desc", net.getIdentifier()); + + searchAndCompareAsListInOrder(querySort, List.of(case1, case2, case3)); + searchAndCompareAsListInOrder(querySort2, List.of(case3, case1, case2)); + } + + @Test + public void testSearchByCreationDate() { + PetriNet net = importPetriNet("search/search_test.xml"); + Case case1 = importHelper.createCase("Search Test", net); + Case case2 = importHelper.createCase("Search Test", net); + Case case3 = importHelper.createCase("Search Test", net); + + String queryEq = String.format("case: creationDate eq %s", toDateTimeString(case1.getCreationDate())); + String queryLt = String.format("cases: processIdentifier eq '%s' and creationDate lt %s", net.getIdentifier(), toDateTimeString(case3.getCreationDate())); + String queryLte = String.format("cases: processIdentifier eq '%s' and creationDate lte %s", net.getIdentifier(), toDateTimeString(case3.getCreationDate())); + String queryGt = String.format("cases: processIdentifier eq '%s' and creationDate gt %s", net.getIdentifier(), toDateTimeString(case1.getCreationDate())); + String queryGte = String.format("cases: processIdentifier eq '%s' and creationDate gte %s", net.getIdentifier(), toDateTimeString(case1.getCreationDate())); + + searchAndCompare(queryEq, case1); + searchAndCompareAsList(queryLt, List.of(case1, case2)); + searchAndCompareAsList(queryLte, List.of(case1, case2, case3)); + searchAndCompareAsList(queryGt, List.of(case2, case3)); + searchAndCompareAsList(queryGte, List.of(case1, case2, case3)); + + // in list + String queryInList = String.format("cases: processIdentifier eq '%s' and creationDate in (%s, %s)", net.getIdentifier(), toDateTimeString(case1.getCreationDate()), toDateTimeString(case3.getCreationDate())); + searchAndCompareAsList(queryInList, List.of(case1, case3)); + + // in range + String queryInRange = String.format("cases: processIdentifier eq '%s' and creationDate in [%s : %s)", net.getIdentifier(), toDateTimeString(case1.getCreationDate()), toDateTimeString(case3.getCreationDate())); + searchAndCompareAsList(queryInRange, List.of(case1, case2)); + + // sort + String querySort = String.format("cases: processIdentifier eq '%s' sort by creationDate", net.getIdentifier()); + String querySort2 = String.format("cases: processIdentifier eq '%s' sort by creationDate desc", net.getIdentifier()); + + searchAndCompareAsListInOrder(querySort, List.of(case1, case2, case3)); + searchAndCompareAsListInOrder(querySort2, List.of(case3, case2, case1)); + } + + @Test + public void testSearchByAuthor() { + PetriNet net = importPetriNet("search/search_test.xml"); + IUser user1 = createUser("Name1", "Surname1", "Email1"); + IUser user2 = createUser("Name2", "Surname2", "Email2"); + Case case1 = importHelper.createCase("Search Test", net, user1.transformToLoggedUser()); + Case case2 = importHelper.createCase("Search Test", net, user1.transformToLoggedUser()); + Case case3 = importHelper.createCase("Search Test2", net, user2.transformToLoggedUser()); + + String query = String.format("case: processIdentifier eq '%s' and author eq '%s'", net.getIdentifier(), user1.getStringId()); + String queryOther = String.format("case: processIdentifier eq '%s' and author eq '%s'", net.getIdentifier(), user2.getStringId()); + String queryMore = String.format("cases: processIdentifier eq '%s' and author eq '%s'", net.getIdentifier(), user1.getStringId()); + + searchAndCompare(query, List.of(case1, case2)); + searchAndCompare(queryOther, case3); + searchAndCompareAsList(queryMore, List.of(case1, case2)); + + // in list + String queryInList = String.format("cases: author in ('%s', '%s')", user1.getStringId(), user2.getStringId()); + + searchAndCompareAsList(queryInList, List.of(case1, case2, case3)); + + // sort + String querySort = String.format("cases: processIdentifier eq '%s' sort by author", net.getIdentifier()); + String querySort2 = String.format("cases: processIdentifier eq '%s' sort by author desc", net.getIdentifier()); + + searchAndCompareAsListInOrder(querySort, List.of(case1, case2, case3)); + searchAndCompareAsListInOrder(querySort2, List.of(case3, case1, case2)); + } + + @Test + public void testSearchByPlaces() throws InterruptedException { + PetriNet net = importPetriNet("search/search_test.xml"); + IUser user1 = createUser("Name1", "Surname1", "Email1"); + Case case1 = importHelper.createCase("Search Test1", net); + Case case2 = importHelper.createCase("Search Test2", net); + Case case3 = importHelper.createCase("Search Test3", net); + + importHelper.assignTask("Test", case1.getStringId(), user1.transformToLoggedUser()); + importHelper.finishTask("Test", case1.getStringId(), user1.transformToLoggedUser()); + importHelper.assignTask("Test", case2.getStringId(), user1.transformToLoggedUser()); + importHelper.finishTask("Test", case2.getStringId(), user1.transformToLoggedUser()); + + String query = String.format("case: processIdentifier eq '%s' AND places.p2.marking eq %s", net.getIdentifier(), 1); + String queryOther = String.format("case: processIdentifier eq '%s' AND places.p1.marking eq %s", net.getIdentifier(), 1); + String queryMore = String.format("cases: processIdentifier eq '%s' AND places.p2.marking eq %s", net.getIdentifier(), 1); + + Thread.sleep(3000); + + searchAndCompare(query, List.of(case1, case2)); + searchAndCompare(queryOther, case3); + searchAndCompareAsList(queryMore, List.of(case1, case2)); + + // in list + String queryInList = String.format("cases: processIdentifier eq '%s' and places.p1.marking in (1)", net.getIdentifier()); + searchAndCompareAsList(queryInList, List.of(case3)); + + // in range + String queryInRange = String.format("cases: processIdentifier eq '%s' and places.p1.marking in [1 : 100]", net.getIdentifier()); + searchAndCompareAsList(queryInRange, List.of(case3)); + + // sort + String querySort = String.format("cases: processIdentifier eq '%s' sort by places.p2.marking", net.getIdentifier()); + String querySort2 = String.format("cases: processIdentifier eq '%s' sort by places.p2.marking desc", net.getIdentifier()); + + searchAndCompareAsListInOrder(querySort, List.of(case1, case2, case3)); + searchAndCompareAsListInOrder(querySort2, List.of(case1, case2, case3)); + } + + @Test + public void testSearchByTaskState() throws InterruptedException { // todo NAE-1997: not indexing tasks.state + PetriNet net = importPetriNet("search/search_test.xml"); + IUser user1 = createUser("Name1", "Surname1", "Email1"); + Case case1 = importHelper.createCase("Search Test", net); + Case case2 = importHelper.createCase("Search Test", net); + Case case3 = importHelper.createCase("Search Test2", net); + + importHelper.assignTask("Test", case1.getStringId(), user1.transformToLoggedUser()); + importHelper.finishTask("Test", case1.getStringId(), user1.transformToLoggedUser()); + importHelper.assignTask("Test", case2.getStringId(), user1.transformToLoggedUser()); + importHelper.finishTask("Test", case2.getStringId(), user1.transformToLoggedUser()); + + String query = String.format("case: tasks.%s.state eq %s", TEST_TRANSITION_ID, "disabled"); + String queryOther = String.format("case: tasks.%s.state eq %s", TEST_TRANSITION_ID, "enabled"); + String queryMore = String.format("cases: tasks.%s.state eq %s", TEST_TRANSITION_ID, "disabled"); + + Thread.sleep(3000); + + searchAndCompare(query, List.of(case1, case2)); + searchAndCompare(queryOther, case3); + searchAndCompareAsList(queryMore, List.of(case1, case2)); + + // sort + String querySort = String.format("cases: processIdentifier eq '%s' sort by tasks.%s.state", net.getIdentifier(), TEST_TRANSITION_ID); + String querySort2 = String.format("cases: processIdentifier eq '%s' sort by tasks.%s.state desc", net.getIdentifier(), TEST_TRANSITION_ID); + + searchAndCompareAsListInOrder(querySort, List.of(case3, case1, case2)); + searchAndCompareAsListInOrder(querySort2, List.of(case1, case2, case3)); + } + + @Test + public void testSearchByTaskUserId() { // todo NAE-1997: not indexing tasks.userId + PetriNet net = importPetriNet("search/search_test.xml"); + IUser user1 = createUser("Name1", "Surname1", "Email1"); + IUser user2 = createUser("Name2", "Surname2", "Email2"); + Case case1 = importHelper.createCase("Search Test", net); + Case case2 = importHelper.createCase("Search Test", net); + Case case3 = importHelper.createCase("Search Test2", net); + + importHelper.assignTask("Test", case1.getStringId(), user1.transformToLoggedUser()); + importHelper.assignTask("Test", case2.getStringId(), user1.transformToLoggedUser()); + importHelper.assignTask("Test", case3.getStringId(), user2.transformToLoggedUser()); + + String query = String.format("case: tasks.%s.userId eq '%s'", TEST_TRANSITION_ID, user1.getStringId()); + String queryOther = String.format("case: tasks.%s.userId eq '%s'", TEST_TRANSITION_ID, user2.getStringId()); + String queryMore = String.format("cases: tasks.%s.userId eq '%s'", TEST_TRANSITION_ID, user1.getStringId()); + + searchAndCompare(query, List.of(case1, case2)); + searchAndCompare(queryOther, case3); + searchAndCompareAsList(queryMore, List.of(case1, case2)); + + // in list + String queryInList = String.format("cases: tasks.%s.userId in ('%s', '%s')", TEST_TRANSITION_ID, user1.getStringId(), user2.getStringId()); + + searchAndCompareAsList(queryInList, List.of(case1, case2, case3)); + + // sort + String querySort = String.format("cases: processIdentifier eq '%s' sort by tasks.%s.userId", net.getIdentifier(), TEST_TRANSITION_ID); + String querySort2 = String.format("cases: processIdentifier eq '%s' sort by tasks.%s.userId desc", net.getIdentifier(), TEST_TRANSITION_ID); + + searchAndCompareAsListInOrder(querySort, List.of(case3, case1, case2)); + searchAndCompareAsListInOrder(querySort2, List.of(case1, case2, case3)); + } + + @Test + public void testSearchByDataValue() throws InterruptedException { + PetriNet net = importPetriNet("search/search_test.xml"); + + IUser user1 = createUser("Name1", "Surname1", "Email1"); + + Case case1 = importHelper.createCase("Search Test", net); + Case case2 = importHelper.createCase("Search Test2", net); + + BooleanField booleanFalse = new BooleanField(); + booleanFalse.setRawValue(false); + TextField textField = new TextField(); + textField.setRawValue("other"); + Map options = Map.of("test1", new I18nString("Test1"), "test2", new I18nString("Test2"), "test3", new I18nString("Test3")); + EnumerationMapField enumerationMapField = new EnumerationMapField(); + enumerationMapField.setOptions(options); + enumerationMapField.setRawValue("test1"); + MultichoiceMapField multichoiceMapField = new MultichoiceMapField(); + multichoiceMapField.setOptions(options); + multichoiceMapField.setRawValue(Set.of("test1", "test2")); + NumberField numberField = new NumberField(); + numberField.setRawValue(2.0); + DateField dateField = new DateField(); + dateField.setRawValue(LocalDate.now().minusDays(5)); + DateTimeField dateTimeField = new DateTimeField(); + dateTimeField.setRawValue(LocalDateTime.now().minusDays(5)); + + importHelper.assignTask("Test", case2.getStringId(), user1.transformToLoggedUser()); + importHelper.setTaskData("Test", case2.getStringId(), new DataSet(Map.of( + "boolean_immediate", booleanFalse, + "text_immediate", textField, + "number_immediate", numberField, + "multichoice_map_immediate", multichoiceMapField, + "enumeration_map_immediate", enumerationMapField, + "date_immediate", dateField, + "date_time_immediate", dateTimeField + ))); + case2 = importHelper.finishTask("Test", case2.getStringId(), user1.transformToLoggedUser()).getCase(); + + String queryTextEq = String.format("case: data.text_immediate.value eq %s", "'test'"); + String queryTextContains = String.format("case: data.text_immediate.value contains %s", "'es'"); + String queryBoolean = String.format("case: data.boolean_immediate.value eq %s", "true"); + String queryEnumerationEq = String.format("case: data.enumeration_map_immediate.value eq %s", "'key2'"); + String queryEnumerationContains = String.format("case: data.enumeration_map_immediate.value contains %s", "'ey2'"); + String queryMultichoiceEq = String.format("case: data.multichoice_map_immediate.value eq %s", "'key2'"); + String queryMultichoiceContains = String.format("case: data.multichoice_map_immediate.value contains %s", "'ey2'"); + String queryNumberEq = String.format("case: data.number_immediate.value eq %s", 54); + String queryNumberLt = String.format("case: data.number_immediate.value lt %s", 55); + String queryNumberLte = String.format("case: data.number_immediate.value lte %s", 55); + String queryNumberGt = String.format("case: data.number_immediate.value gt %s", 53); + String queryNumberGte = String.format("case: data.number_immediate.value gte %s", 53); + String queryDateEq = String.format("case: data.date_immediate.value eq %s", SearchUtils.toDateString(LocalDate.now())); + String queryDateLt = String.format("case: data.date_immediate.value lt %s", SearchUtils.toDateString(LocalDate.now().plusDays(1))); + String queryDateLte = String.format("case: data.date_immediate.value lte %s", SearchUtils.toDateString(LocalDate.now().plusDays(1))); + String queryDateGt = String.format("case: data.date_immediate.value gt %s", SearchUtils.toDateString(LocalDate.now().minusDays(1))); + String queryDateGte = String.format("case: data.date_immediate.value gte %s", SearchUtils.toDateString(LocalDate.now().minusDays(1))); + String queryDateTimeEq = String.format("case: data.date_time_immediate.value eq %s", SearchUtils.toDateTimeString((LocalDateTime) case1.getDataSet().get("date_time_immediate").getRawValue())); + String queryDateTimeLt = String.format("case: data.date_time_immediate.value lt %s", SearchUtils.toDateTimeString(LocalDateTime.now().plusMinutes(1))); + String queryDateTimeLte = String.format("case: data.date_time_immediate.value lte %s", SearchUtils.toDateTimeString(LocalDateTime.now().plusMinutes(1))); + String queryDateTimeGt = String.format("case: data.date_time_immediate.value gt %s", SearchUtils.toDateTimeString(LocalDateTime.now().minusMinutes(1))); + String queryDateTimeGte = String.format("case: data.date_time_immediate.value gte %s", SearchUtils.toDateTimeString(LocalDateTime.now().minusMinutes(1))); + + Thread.sleep(3000); + + // text + searchAndCompare(queryTextEq, case1); + searchAndCompare(queryTextContains, case1); + + // boolean + searchAndCompare(queryBoolean, case1); + + // number + searchAndCompare(queryNumberEq, case1); + searchAndCompare(queryNumberLt, List.of(case1, case2)); + searchAndCompare(queryNumberLte, List.of(case1, case2)); + searchAndCompare(queryNumberGt, case1); + searchAndCompare(queryNumberGte, case1); + + // date + searchAndCompare(queryDateEq, case1); + searchAndCompare(queryDateLt, List.of(case1, case2)); + searchAndCompare(queryDateLte, List.of(case1, case2)); + searchAndCompare(queryDateGt, case1); + searchAndCompare(queryDateGte, case1); + + // datetime + searchAndCompare(queryDateTimeEq, case1); + searchAndCompare(queryDateTimeLt, List.of(case1, case2)); + searchAndCompare(queryDateTimeLte, List.of(case1, case2)); + searchAndCompare(queryDateTimeGt, case1); + searchAndCompare(queryDateTimeGte, case1); + + // enumeration/multichoice + // todo NAE-1997: should use keyValue, textValue represents only value +// searchAndCompare(queryEnumerationEq, case1); +// searchAndCompare(queryEnumerationContains, case1); +// +// searchAndCompare(queryMultichoiceEq, case1); +// searchAndCompare(queryMultichoiceContains, case1); + + // in list text + String queryInList = String.format("cases: processIdentifier eq '%s' and data.text_immediate.value in ('test', 'other')", net.getIdentifier()); + searchAndCompareAsList(queryInList, List.of(case1, case2)); + + // in range text + String queryInRange = String.format("cases: processIdentifier eq '%s' and data.text_immediate.value in ['other' : 'test')", net.getIdentifier()); + searchAndCompareAsList(queryInRange, List.of(case2)); + + // in list number + queryInList = String.format("cases: processIdentifier eq '%s' and data.number_immediate.value in (2, 54)", net.getIdentifier()); + searchAndCompareAsList(queryInList, List.of(case1, case2)); + + // in range number + queryInRange = String.format("cases: processIdentifier eq '%s' and data.number_immediate.value in [2 : 54)", net.getIdentifier()); + searchAndCompareAsList(queryInRange, List.of(case2)); + + // in list date + queryInList = String.format("cases: processIdentifier eq '%s' and data.date_immediate.value in (%s, %s)", net.getIdentifier(), toDateString(LocalDateTime.now()), toDateString(LocalDateTime.now().minusDays(5))); + searchAndCompareAsList(queryInList, List.of(case1, case2)); + + // in range date + queryInRange = String.format("cases: processIdentifier eq '%s' and data.date_immediate.value in [%s : %s)", net.getIdentifier(), toDateString(LocalDateTime.now().minusDays(10)), toDateString(LocalDateTime.now().plusDays(1))); + searchAndCompareAsList(queryInRange, List.of(case1, case2)); + + // in list datetime + LocalDateTime localDateTime1 = (LocalDateTime) case1.getDataSet().get("date_time_immediate").getRawValue(); + LocalDateTime localDateTime2 = (LocalDateTime) case2.getDataSet().get("date_time_immediate").getRawValue(); + queryInList = String.format("cases: processIdentifier eq '%s' and data.date_time_immediate.value in (%s, %s)", net.getIdentifier(), toDateTimeString(localDateTime1), toDateTimeString(localDateTime2)); + searchAndCompareAsList(queryInList, List.of(case1, case2)); + + // in range datetime + queryInRange = String.format("cases: processIdentifier eq '%s' and data.date_time_immediate.value in [%s : %s)", net.getIdentifier(), toDateTimeString(localDateTime2), toDateTimeString(localDateTime1)); + searchAndCompareAsList(queryInRange, List.of(case2)); + + // todo NAE-1997: sort by data - indexation change needed + } + + @Test + public void testSearchByDataOptions() throws InterruptedException { + PetriNet net = importPetriNet("search/search_test.xml"); + + Case case1 = importHelper.createCase("Search Test", net); + + String queryEq = String.format("case: data.enumeration_map_immediate.options eq '%s'", "key1"); + String queryContains = String.format("case: data.enumeration_map_immediate.options contains '%s'", "key1"); + + Thread.sleep(3000); + + searchAndCompare(queryEq, case1); + searchAndCompare(queryContains, case1); + + // in list + String queryInList = String.format("cases: processIdentifier eq '%s' and data.enumeration_map_immediate.options in ('key1')", net.getIdentifier()); + searchAndCompareAsList(queryInList, List.of(case1)); + + // in range + String queryInRange = String.format("cases: processIdentifier eq '%s' and data.enumeration_map_immediate.options in ['key1' : 'key2')", net.getIdentifier()); + searchAndCompareAsList(queryInRange, List.of(case1)); + + // todo NAE-1997: sort by data - indexation change needed + } + + @Test + public void testPagination() { + PetriNet net = importPetriNet("search/search_test.xml"); + List cases = new ArrayList<>(); + for (int i = 0; i < 50; i++) { + cases.add(importHelper.createCase("Search Test", net)); + } + + String queryOne = String.format("case: processIdentifier eq '%s'", "search_test"); + String queryMore = String.format("cases: processIdentifier eq '%s'", "search_test"); + String queryMoreCustomPagination = String.format("cases: processIdentifier eq '%s' page 1 size 5", "search_test"); + + long count = searchService.count(queryOne); + assert count == 50; + + Object actual = searchService.search(queryOne); + compareById(convertToObject(actual, Case.class), cases.get(0), Case::getStringId); + + actual = searchService.search(queryMore); + compareById(convertToObjectList(actual, Case.class), cases.subList(0, 19), Case::getStringId); + + actual = searchService.search(queryMoreCustomPagination); + compareById(convertToObjectList(actual, Case.class), cases.subList(5, 9), Case::getStringId); + } +} diff --git a/src/test/java/com/netgrif/application/engine/search/SearchProcessTest.java b/src/test/java/com/netgrif/application/engine/search/SearchProcessTest.java new file mode 100644 index 00000000000..64971aa1025 --- /dev/null +++ b/src/test/java/com/netgrif/application/engine/search/SearchProcessTest.java @@ -0,0 +1,265 @@ +package com.netgrif.application.engine.search; + +import com.netgrif.application.engine.TestHelper; +import com.netgrif.application.engine.petrinet.domain.PetriNet; +import com.netgrif.application.engine.petrinet.domain.VersionType; +import com.netgrif.application.engine.petrinet.domain.repositories.PetriNetRepository; +import com.netgrif.application.engine.search.interfaces.ISearchService; +import com.netgrif.application.engine.startup.ImportHelper; +import lombok.extern.slf4j.Slf4j; +import org.junit.jupiter.api.BeforeEach; +import org.junit.jupiter.api.Test; +import org.junit.jupiter.api.extension.ExtendWith; +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.boot.test.context.SpringBootTest; +import org.springframework.test.context.ActiveProfiles; +import org.springframework.test.context.junit.jupiter.SpringExtension; + +import java.util.ArrayList; +import java.util.List; +import java.util.stream.Collectors; + +import static com.netgrif.application.engine.search.utils.SearchTestUtils.*; +import static com.netgrif.application.engine.search.utils.SearchUtils.toDateTimeString; + +@Slf4j +@SpringBootTest +@ActiveProfiles({"test"}) +@ExtendWith(SpringExtension.class) +public class SearchProcessTest { + @Autowired + private PetriNetRepository petriNetRepository; + + @Autowired + private ImportHelper importHelper; + + @Autowired + private TestHelper testHelper; + + @Autowired + private ISearchService searchService; + + @BeforeEach + void setup() { + testHelper.truncateDbs(); + List idsToDelete = new ArrayList<>(); + idsToDelete.addAll(petriNetRepository.findAllByIdentifier("search_test").stream().map(PetriNet::getStringId).collect(Collectors.toList())); + idsToDelete.addAll(petriNetRepository.findAllByIdentifier("search_test2").stream().map(PetriNet::getStringId).collect(Collectors.toList())); + if (!idsToDelete.isEmpty()) { + petriNetRepository.deleteAllById(idsToDelete); + } + } + + private PetriNet importPetriNet(String fileName, VersionType versionType) { + PetriNet testNet = importHelper.createNet(fileName, versionType).orElse(null); + assert testNet != null; + return testNet; + } + + private void searchAndCompare(String query, PetriNet expected) { + long count = searchService.count(query); + assert count == 1; + + Object actual = searchService.search(query); + compareById(convertToObject(actual, PetriNet.class), expected, PetriNet::getStringId); + } + + private void searchAndCompare(String query, List expected) { + long count = searchService.count(query); + assert count == expected.size(); + + Object actual = searchService.search(query); + compareById(convertToObject(actual, PetriNet.class), expected, PetriNet::getStringId); + } + + private void searchAndCompareAsList(String query, List expected) { + long count = searchService.count(query); + assert count == expected.size(); + + Object actual = searchService.search(query); + compareById(convertToObjectList(actual, PetriNet.class), expected, PetriNet::getStringId); + } + + private void searchAndCompareAsListInOrder(String query, List expected) { + long count = searchService.count(query); + assert count == expected.size(); + + Object actual = searchService.search(query); + compareByIdInOrder(convertToObjectList(actual, PetriNet.class), expected, PetriNet::getStringId); + } + + @Test + public void testSearchById() { + PetriNet net = importPetriNet("search/search_test.xml", VersionType.MAJOR); + PetriNet net2 = importPetriNet("search/search_test.xml", VersionType.MAJOR); + + String query = String.format("process: id eq '%s'", net.getStringId()); + + searchAndCompare(query, net); + + // in list + String queryInList = String.format("processes: id in ('%s', '%s')", net.getStringId(), net2.getStringId()); + + searchAndCompareAsList(queryInList, List.of(net, net2)); + + // sort + query = String.format("processes: identifier eq '%s' sort by id", net.getIdentifier()); + searchAndCompareAsListInOrder(query, List.of(net, net2)); + + query = String.format("processes: identifier eq '%s' sort by id desc", net.getIdentifier()); + searchAndCompareAsListInOrder(query, List.of(net2, net)); + } + + @Test + public void testSearchByIdentifier() { + PetriNet net = importPetriNet("search/search_test.xml", VersionType.MAJOR); + PetriNet netNewer = importPetriNet("search/search_test.xml", VersionType.MAJOR); + PetriNet net2 = importPetriNet("search/search_test2.xml", VersionType.MAJOR); + PetriNet net3 = importPetriNet("search/search_test3.xml", VersionType.MAJOR); + + String query = String.format("process: identifier eq '%s'", net.getIdentifier()); + String queryMore = String.format("processes: identifier eq '%s'", net.getIdentifier()); + + searchAndCompare(query, List.of(net, netNewer)); + searchAndCompareAsList(queryMore, List.of(net, netNewer)); + + // in list + String queryInList = String.format("processes: identifier in ('%s', '%s', '%s')", net.getIdentifier(), net2.getIdentifier(), net3.getIdentifier()); + searchAndCompareAsList(queryInList, List.of(net, netNewer, net2, net3)); + + // in range + String queryInRange = String.format("processes: identifier in ['%s' : '%s')", net.getIdentifier(), net3.getIdentifier()); + searchAndCompareAsList(queryInRange, List.of(net, netNewer, net2)); + + // sort + queryMore = String.format("processes: identifier in ('%s', '%s') sort by identifier", net.getIdentifier(), net2.getIdentifier()); + searchAndCompareAsListInOrder(queryMore, List.of(net, netNewer, net2)); + + queryMore = String.format("processes: identifier in ('%s', '%s') sort by identifier desc", net.getIdentifier(), net2.getIdentifier()); + searchAndCompareAsListInOrder(queryMore, List.of(net2, net, netNewer)); + } + + @Test + public void testSearchByVersion() { + PetriNet net = importPetriNet("search/search_test.xml", VersionType.MAJOR); + PetriNet netNewerPatch = importPetriNet("search/search_test.xml", VersionType.PATCH); + PetriNet netNewerMinor = importPetriNet("search/search_test.xml", VersionType.MINOR); + PetriNet netNewerMajor = importPetriNet("search/search_test.xml", VersionType.MAJOR); + + String queryEq = String.format("process: identifier eq '%s' and version eq %s", net.getIdentifier(), "1.0.0"); + String queryLt = String.format("processes: identifier eq '%s' and version lt %s", net.getIdentifier(), "2.0.0"); + String queryLte = String.format("processes: identifier eq '%s' and version lte %s", net.getIdentifier(), "2.0.0"); + String queryGt = String.format("processes: identifier eq '%s' and version gt %s", net.getIdentifier(), "1.0.0"); + String queryGte = String.format("processes: identifier eq '%s' and version gte %s", net.getIdentifier(), "1.0.0"); + + searchAndCompare(queryEq, net); + searchAndCompareAsList(queryLt, List.of(net, netNewerPatch, netNewerMinor)); + searchAndCompareAsList(queryLte, List.of(net, netNewerPatch, netNewerMinor, netNewerMajor)); + searchAndCompareAsList(queryGt, List.of(netNewerPatch, netNewerMinor, netNewerMajor)); + searchAndCompareAsList(queryGte, List.of(net, netNewerPatch, netNewerMinor, netNewerMajor)); + + // in list + String queryInList = String.format("processes: identifier eq '%s' and version in (%s, %s, %s)", net.getIdentifier(), "1.0.0", "1.0.1", "1.1.0"); + searchAndCompareAsList(queryInList, List.of(net, netNewerPatch, netNewerMinor)); + + // in range + String queryInRange = String.format("processes: identifier eq '%s' and version in [%s : %s)", net.getIdentifier(), "1.0.0", "1.1.0"); + searchAndCompareAsList(queryInRange, List.of(net, netNewerPatch)); + + // sort + String query = String.format("processes: identifier eq '%s' sort by version", net.getIdentifier()); + searchAndCompareAsListInOrder(query, List.of(net, netNewerPatch, netNewerMinor, netNewerMajor)); + + query = String.format("processes: identifier eq '%s' sort by version desc", net.getIdentifier()); + searchAndCompareAsListInOrder(query, List.of(netNewerMajor, netNewerMinor, netNewerPatch, net)); + } + + @Test + public void testSearchByTitle() { + PetriNet net = importPetriNet("search/search_test.xml", VersionType.MAJOR); + PetriNet netNewer = importPetriNet("search/search_test.xml", VersionType.MAJOR); + PetriNet net2 = importPetriNet("search/search_test2.xml", VersionType.MAJOR); + + String query = String.format("process: title eq '%s'", net.getTitle().toString()); + String queryOther = String.format("process: title eq '%s'", net2.getTitle().toString()); + String queryMore = String.format("processes: title eq '%s'", net.getTitle().toString()); + + searchAndCompare(query, List.of(net, netNewer)); + searchAndCompare(queryOther, net2); + searchAndCompareAsList(queryMore, List.of(net, netNewer)); + + // in list + String queryInList = String.format("processes: identifier in ('%s', '%s') and title in ('%s', '%s')", net.getIdentifier(), net2.getIdentifier(), net.getTitle().getDefaultValue(), net2.getTitle().getDefaultValue()); + searchAndCompareAsList(queryInList, List.of(net, netNewer, net2)); + + // in range + String queryInRange = String.format("processes: identifier in ('%s', '%s') and title in ['%s' : '%s')", net.getIdentifier(), net2.getIdentifier(), net.getTitle().getDefaultValue(), net2.getTitle().getDefaultValue()); + searchAndCompareAsList(queryInRange, List.of(net, netNewer)); + + // sort + queryMore = String.format("processes: identifier in ('%s', '%s') sort by title", net.getIdentifier(), net2.getIdentifier()); + searchAndCompareAsListInOrder(queryMore, List.of(net, netNewer, net2)); + + queryMore = String.format("processes: identifier in ('%s', '%s') sort by title desc", net.getIdentifier(), net2.getIdentifier()); + searchAndCompareAsListInOrder(queryMore, List.of(net2, net, netNewer)); + } + + @Test + public void testSearchByCreationDate() { + PetriNet net = importPetriNet("search/search_test.xml", VersionType.MAJOR); + PetriNet netNewer = importPetriNet("search/search_test.xml", VersionType.MAJOR); + PetriNet netNewest = importPetriNet("search/search_test.xml", VersionType.MAJOR); + + String queryEq = String.format("process: identifier eq '%s' and creationDate eq %s", net.getIdentifier(), toDateTimeString(net.getCreationDate())); + String queryLt = String.format("processes: identifier eq '%s' and creationDate lt %s", net.getIdentifier(), toDateTimeString(netNewest.getCreationDate())); + String queryLte = String.format("processes: identifier eq '%s' and creationDate lte %s", net.getIdentifier(), toDateTimeString(netNewest.getCreationDate())); + String queryGt = String.format("processes: identifier eq '%s' and creationDate gt %s", net.getIdentifier(), toDateTimeString(net.getCreationDate())); + String queryGte = String.format("processes: identifier eq '%s' and creationDate gte %s", net.getIdentifier(), toDateTimeString(net.getCreationDate())); + + searchAndCompare(queryEq, net); + searchAndCompareAsList(queryLt, List.of(net, netNewer)); + searchAndCompareAsList(queryLte, List.of(net, netNewer, netNewest)); + searchAndCompareAsList(queryGt, List.of(netNewer, netNewest)); + searchAndCompareAsList(queryGte, List.of(net, netNewer, netNewest)); + + // in list + String queryInList = String.format("processes: identifier eq '%s' and creationDate in (%s, %s)", net.getIdentifier(), toDateTimeString(net.getCreationDate()), toDateTimeString(netNewest.getCreationDate())); + searchAndCompareAsList(queryInList, List.of(net, netNewest)); + + // in range + String queryInRange = String.format("processes: identifier eq '%s' and creationDate in [%s : %s)", net.getIdentifier(), toDateTimeString(net.getCreationDate()), toDateTimeString(netNewest.getCreationDate())); + searchAndCompareAsList(queryInRange, List.of(net, netNewer)); + + // sort + String query = String.format("processes: identifier eq '%s' sort by creationDate", net.getIdentifier()); + searchAndCompareAsListInOrder(query, List.of(net, netNewer, netNewest)); + + query = String.format("processes: identifier eq '%s' sort by creationDate desc", net.getIdentifier()); + searchAndCompareAsListInOrder(query, List.of(netNewest, netNewer, net)); + } + + @Test + public void testPagination() { + List nets = new ArrayList<>(); + for (int i = 0; i < 50; i++) { + nets.add(importPetriNet("search/search_test.xml", VersionType.MAJOR)); + } + + String queryOne = String.format("process: identifier eq '%s'", "search_test"); + String queryMore = String.format("processes: identifier eq '%s'", "search_test"); + String queryMoreCustomPagination = String.format("processes: identifier eq '%s' page 1 size 5", "search_test"); + + long count = searchService.count(queryOne); + assert count == 50; + + Object process = searchService.search(queryOne); + compareById(convertToObject(process, PetriNet.class), nets.get(0), PetriNet::getStringId); + + Object processes = searchService.search(queryMore); + compareById(convertToObjectList(processes, PetriNet.class), nets.subList(0, 19), PetriNet::getStringId); + + processes = searchService.search(queryMoreCustomPagination); + compareById(convertToObjectList(processes, PetriNet.class), nets.subList(5, 9), PetriNet::getStringId); + } + +} diff --git a/src/test/java/com/netgrif/application/engine/search/SearchTaskTest.java b/src/test/java/com/netgrif/application/engine/search/SearchTaskTest.java new file mode 100644 index 00000000000..165c675866a --- /dev/null +++ b/src/test/java/com/netgrif/application/engine/search/SearchTaskTest.java @@ -0,0 +1,504 @@ +package com.netgrif.application.engine.search; + +import com.netgrif.application.engine.TestHelper; +import com.netgrif.application.engine.auth.domain.Authority; +import com.netgrif.application.engine.auth.domain.IUser; +import com.netgrif.application.engine.auth.domain.User; +import com.netgrif.application.engine.petrinet.domain.I18nString; +import com.netgrif.application.engine.petrinet.domain.PetriNet; +import com.netgrif.application.engine.petrinet.domain.roles.ProcessRole; +import com.netgrif.application.engine.search.interfaces.ISearchService; +import com.netgrif.application.engine.search.utils.SearchUtils; +import com.netgrif.application.engine.startup.ImportHelper; +import com.netgrif.application.engine.workflow.domain.Case; +import com.netgrif.application.engine.workflow.domain.State; +import com.netgrif.application.engine.workflow.domain.Task; +import com.netgrif.application.engine.workflow.service.interfaces.ITaskService; +import lombok.extern.slf4j.Slf4j; +import org.junit.jupiter.api.BeforeEach; +import org.junit.jupiter.api.Test; +import org.junit.jupiter.api.extension.ExtendWith; +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.boot.test.context.SpringBootTest; +import org.springframework.test.context.ActiveProfiles; +import org.springframework.test.context.junit.jupiter.SpringExtension; + +import java.time.LocalDateTime; +import java.util.ArrayList; +import java.util.Comparator; +import java.util.List; +import java.util.Map; +import java.util.stream.Collectors; + +import static com.netgrif.application.engine.search.utils.SearchTestUtils.*; +import static com.netgrif.application.engine.search.utils.SearchUtils.toDateTimeString; + +@Slf4j +@SpringBootTest +@ActiveProfiles({"test"}) +@ExtendWith(SpringExtension.class) +public class SearchTaskTest { + public static final String TEST_TRANSITION_ID = "search_test_t1"; + public static final String TEST_TRANSITION2_ID = "search_test_t2"; + + @Autowired + private ImportHelper importHelper; + + @Autowired + private TestHelper testHelper; + + @Autowired + private ISearchService searchService; + + @Autowired + private ITaskService taskService; + + private Map auths; + + @BeforeEach + void setup() { + testHelper.truncateDbs(); + auths = importHelper.createAuthorities(Map.of("user", Authority.user, "admin", Authority.admin)); + } + + private PetriNet importPetriNet(String fileName) { + PetriNet testNet = importHelper.createNet(fileName).orElse(null); + assert testNet != null; + return testNet; + } + + private IUser createUser(String name, String surname, String email) { + User user = new User(email, "password", name, surname); + Authority[] authorities = new Authority[]{auths.get("user")}; + ProcessRole[] processRoles = new ProcessRole[]{}; + return importHelper.createUser(user, authorities, processRoles); + } + + private void searchAndCompare(String query, Task expectedResult) { + long count = searchService.count(query); + assert count == 1; + + Object actual = searchService.search(query); + compareById(convertToObject(actual, Task.class), expectedResult, Task::getStringId); + } + + private void searchAndCompare(String query, List expected) { + long count = searchService.count(query); + assert count == expected.size(); + + Object actual = searchService.search(query); + compareById(convertToObject(actual, Task.class), expected, Task::getStringId); + } + + private void searchAndCompareAsList(String query, List expected) { + long count = searchService.count(query); + assert count == expected.size(); + + Object actual = searchService.search(query); + compareById(convertToObjectList(actual, Task.class), expected, Task::getStringId); + } + + private void searchAndCompareAsListInOrder(String query, List expected) { + long count = searchService.count(query); + assert count == expected.size(); + + Object actual = searchService.search(query); + compareByIdInOrder(convertToObjectList(actual, Task.class), expected, Task::getStringId); + } + + @Test + public void testSearchById() { + PetriNet net = importPetriNet("search/search_test.xml"); + Case caze = importHelper.createCase("Search Test", net); + String taskId = caze.getTasks().get(TEST_TRANSITION_ID).getTaskStringId(); + Task task = taskService.findOne(taskId); + String task2Id = caze.getTasks().get(TEST_TRANSITION2_ID).getTaskStringId(); + Task task2 = taskService.findOne(task2Id); + + String query = String.format("task: id eq '%s'", taskId); + + searchAndCompare(query, task); + + // in list + String queryInList = String.format("tasks: id in ('%s', '%s')", task.getStringId(), task2.getStringId()); + + searchAndCompareAsList(queryInList, List.of(task, task2)); + + // sort + String querySort = String.format("tasks: caseId eq '%s' sort by id", caze.getStringId()); + String querySort2 = String.format("tasks: caseId eq '%s' sort by id desc", caze.getStringId()); + + List case1Tasks = caze.getTasks().values().stream() + .map(taskPair -> taskService.findOne(taskPair.getTaskStringId())) + .collect(Collectors.toList()); + List asc = case1Tasks.stream() + .sorted(Comparator.comparing(Task::getStringId)) + .collect(Collectors.toList()); + List desc = case1Tasks.stream() + .sorted(Comparator.comparing(Task::getStringId).reversed()) + .collect(Collectors.toList()); + + searchAndCompareAsListInOrder(querySort, asc); + searchAndCompareAsListInOrder(querySort2, desc); + } + + @Test + public void testSearchByTransitionId() { + PetriNet net = importPetriNet("search/search_test.xml"); + Case case1 = importHelper.createCase("Search Test", net); + String taskId = case1.getTasks().get(TEST_TRANSITION_ID).getTaskStringId(); + Task task = taskService.findOne(taskId); + String task2Id = case1.getTasks().get(TEST_TRANSITION2_ID).getTaskStringId(); + Task task2 = taskService.findOne(task2Id); + + String query = String.format("task: transitionId eq '%s'", TEST_TRANSITION_ID); + String queryMore = String.format("tasks: transitionId eq '%s'", TEST_TRANSITION_ID); + + searchAndCompare(query, task); + searchAndCompareAsList(queryMore, List.of(task)); + + // in list + String queryInList = String.format("tasks: transitionId in ('%s', '%s')", TEST_TRANSITION_ID, TEST_TRANSITION2_ID); + + searchAndCompareAsList(queryInList, List.of(task, task2)); + + // in range + String queryInRange = String.format("tasks: transitionId in ('%s' : '%s']", TEST_TRANSITION_ID, TEST_TRANSITION2_ID); + + searchAndCompareAsList(queryInRange, List.of(task2)); + + // sort + String querySort = String.format("tasks: caseId eq '%s' sort by transitionId", case1.getStringId()); + String querySort2 = String.format("tasks: caseId eq '%s' sort by transitionId desc", case1.getStringId()); + + List case1Tasks = case1.getTasks().values().stream() + .map(taskPair -> taskService.findOne(taskPair.getTaskStringId())) + .collect(Collectors.toList()); + List asc = case1Tasks.stream() + .sorted(Comparator.comparing(Task::getTransitionId)) + .collect(Collectors.toList()); + List desc = case1Tasks.stream() + .sorted(Comparator.comparing(Task::getTransitionId).reversed()) + .collect(Collectors.toList()); + + searchAndCompareAsListInOrder(querySort, asc); + searchAndCompareAsListInOrder(querySort2, desc); + } + + @Test + public void testSearchByTitle() { + PetriNet net = importPetriNet("search/search_test.xml"); + Case case1 = importHelper.createCase("Search Test", net); + Case case2 = importHelper.createCase("Search Test", net); + Case case3 = importHelper.createCase("Search Test2", net); + String taskId = case1.getTasks().get(TEST_TRANSITION_ID).getTaskStringId(); + Task task = taskService.findOne(taskId); + String task2Id = case2.getTasks().get(TEST_TRANSITION_ID).getTaskStringId(); + Task task2 = taskService.findOne(task2Id); + String task3Id = case3.getTasks().get(TEST_TRANSITION_ID).getTaskStringId(); + Task task3 = taskService.findOne(task3Id); + task3.setTitle(new I18nString("Zzz")); + taskService.save(task3); + + String query = String.format("task: title eq '%s'", task.getTitle().getDefaultValue()); + String queryMore = String.format("tasks: title eq '%s'", task.getTitle().getDefaultValue()); + + searchAndCompare(query, List.of(task, task2)); + searchAndCompareAsList(queryMore, List.of(task, task2)); + + // in list + String queryInList = String.format("tasks: transitionId eq '%s' and title in ('%s', '%s')", TEST_TRANSITION_ID, task.getTitle().getDefaultValue(), task3.getTitle().getDefaultValue()); + + searchAndCompareAsList(queryInList, List.of(task, task2, task3)); + + // in range + String queryInRange = String.format("tasks: transitionId eq '%s' and title in ('%s' : '%s']", TEST_TRANSITION_ID, task.getTitle().getDefaultValue(), task3.getTitle().getDefaultValue()); + + searchAndCompareAsList(queryInRange, List.of(task3)); + + // sort + String querySort = String.format("tasks: transitionId eq '%s' sort by title", TEST_TRANSITION_ID); + String querySort2 = String.format("tasks: transitionId eq '%s' sort by title desc", TEST_TRANSITION_ID); + + searchAndCompareAsListInOrder(querySort, List.of(task, task2, task3)); + searchAndCompareAsListInOrder(querySort2, List.of(task3, task, task2)); + } + + @Test + public void testSearchByState() { + PetriNet net = importPetriNet("search/search_test.xml"); + IUser user1 = createUser("Name1", "Surname1", "Email1"); + Case case1 = importHelper.createCase("Search Test", net); + importHelper.assignTask("Test", case1.getStringId(), user1.transformToLoggedUser()); + case1 = importHelper.finishTask("Test", case1.getStringId(), user1.transformToLoggedUser()).getCase(); + List case1Tasks = case1.getTasks().values().stream() + .map(taskPair -> taskService.findOne(taskPair.getTaskStringId())) + .collect(Collectors.toList()); + List disabled = case1Tasks.stream() + .filter(task -> task.getState().equals(State.DISABLED)) + .sorted(Comparator.comparing(Task::getStringId)) + .collect(Collectors.toList()); + List enabled = case1Tasks.stream() + .filter(task -> task.getState().equals(State.ENABLED)) + .sorted(Comparator.comparing(Task::getStringId)) + .collect(Collectors.toList()); + + String query = String.format("task: processId eq '%s' and state eq %s", net.getStringId(), "disabled"); + String queryOther = String.format("tasks: processId eq '%s' and state eq %s", net.getStringId(), "enabled"); + String queryMore = String.format("tasks: processId eq '%s' and state eq %s", net.getStringId(), "disabled"); + + searchAndCompare(query, disabled); + searchAndCompareAsList(queryOther, enabled); + searchAndCompareAsList(queryMore, disabled); + + // sort + String querySort = String.format("tasks: processId eq '%s' sort by state", net.getStringId()); + String querySort2 = String.format("tasks: processId eq '%s' sort by state desc", net.getStringId()); + + List asc = new ArrayList<>(); + asc.addAll(disabled); + asc.addAll(enabled); + List desc = new ArrayList<>(); + desc.addAll(enabled); + desc.addAll(disabled); + searchAndCompareAsListInOrder(querySort, asc); + searchAndCompareAsListInOrder(querySort2, desc); + } + + @Test + public void testSearchByUserId() { + PetriNet net = importPetriNet("search/search_test.xml"); + IUser user1 = createUser("Name1", "Surname1", "Email1"); + IUser user2 = createUser("Name2", "Surname2", "Email2"); + Case case1 = importHelper.createCase("Search Test", net); + Case case2 = importHelper.createCase("Search Test", net); + Case case3 = importHelper.createCase("Search Test2", net); + String taskId = case1.getTasks().get(TEST_TRANSITION_ID).getTaskStringId(); + Task task = taskService.findOne(taskId); + String task2Id = case2.getTasks().get(TEST_TRANSITION_ID).getTaskStringId(); + Task task2 = taskService.findOne(task2Id); + String task3Id = case3.getTasks().get(TEST_TRANSITION_ID).getTaskStringId(); + Task task3 = taskService.findOne(task3Id); + importHelper.assignTask("Test", case1.getStringId(), user1.transformToLoggedUser()); + importHelper.assignTask("Test", case2.getStringId(), user1.transformToLoggedUser()); + importHelper.assignTask("Test", case3.getStringId(), user2.transformToLoggedUser()); + + String query = String.format("task: userId eq '%s'", user1.getStringId()); + String queryOther = String.format("task: userId eq '%s'", user2.getStringId()); + String queryMore = String.format("tasks: userId eq '%s'", user1.getStringId()); + + searchAndCompare(query, List.of(task, task2)); + searchAndCompare(queryOther, List.of(task3)); + searchAndCompareAsList(queryMore, List.of(task, task2)); + + // in list + String queryInList = String.format("tasks: userId in ('%s', '%s')", user1.getStringId(), user2.getStringId()); + + searchAndCompareAsList(queryInList, List.of(task, task2, task3)); + + // sort + String querySort = String.format("tasks: transitionId eq '%s' sort by userId", TEST_TRANSITION_ID); + String querySort2 = String.format("tasks: transitionId eq '%s' sort by userId desc", TEST_TRANSITION_ID); + + searchAndCompareAsListInOrder(querySort, List.of(task, task2, task3)); + searchAndCompareAsListInOrder(querySort2, List.of(task3, task, task2)); + } + + @Test + public void testSearchByCaseId() { + PetriNet net = importPetriNet("search/search_test.xml"); + Case case1 = importHelper.createCase("Search Test", net); + Case case2 = importHelper.createCase("Search Test", net); + List case1Tasks = case1.getTasks().values().stream() + .map(taskPair -> taskService.findOne(taskPair.getTaskStringId())) + .sorted(Comparator.comparing(Task::getStringId)) + .collect(Collectors.toList()); + List case2Tasks = case2.getTasks().values().stream() + .map(taskPair -> taskService.findOne(taskPair.getTaskStringId())) + .sorted(Comparator.comparing(Task::getStringId)) + .collect(Collectors.toList()); + + String query = String.format("task: caseId eq '%s'", case1.getStringId()); + String queryOther = String.format("task: caseId eq '%s'", case2.getStringId()); + String queryMore = String.format("tasks: caseId eq '%s'", case1.getStringId()); + + searchAndCompare(query, case1Tasks); + searchAndCompare(queryOther, case2Tasks); + searchAndCompareAsList(queryMore, case1Tasks); + + // in list + String queryInList = String.format("tasks: caseId in ('%s', '%s')", case1.getStringId(), case2.getStringId()); + + List allTasks = new ArrayList<>(); + allTasks.addAll(case1Tasks); + allTasks.addAll(case2Tasks); + searchAndCompareAsList(queryInList, allTasks); + + // sort + String querySort = String.format("tasks: processId eq '%s' sort by caseId", net.getStringId()); + String querySort2 = String.format("tasks: processId eq '%s' sort by caseId desc", net.getStringId()); + + List asc = new ArrayList<>(); + asc.addAll(case1Tasks); + asc.addAll(case2Tasks); + List desc = new ArrayList<>(); + desc.addAll(case2Tasks); + desc.addAll(case1Tasks); + + searchAndCompareAsListInOrder(querySort, asc); + searchAndCompareAsListInOrder(querySort2, desc); + } + + @Test + public void testSearchByProcessId() { + PetriNet net = importPetriNet("search/search_test.xml"); + PetriNet net2 = importPetriNet("search/search_test2.xml"); + Case case1 = importHelper.createCase("Search Test", net); + Case case2 = importHelper.createCase("Search Test2", net2); + List netTasks = case1.getTasks().values().stream() + .map(taskPair -> taskService.findOne(taskPair.getTaskStringId())) + .sorted(Comparator.comparing(Task::getStringId)) + .collect(Collectors.toList()); + List net2Tasks = case2.getTasks().values().stream() + .map(taskPair -> taskService.findOne(taskPair.getTaskStringId())) + .sorted(Comparator.comparing(Task::getStringId)) + .collect(Collectors.toList()); + + String query = String.format("task: processId eq '%s'", net.getStringId()); + String queryOther = String.format("task: processId eq '%s'", net2.getStringId()); + String queryMore = String.format("tasks: processId eq '%s'", net.getStringId()); + + searchAndCompare(query, netTasks); + searchAndCompare(queryOther, net2Tasks); + searchAndCompareAsList(queryMore, netTasks); + + // in list + String queryInList = String.format("tasks: processId in ('%s', '%s')", net.getStringId(), net2.getStringId()); + + List allTasks = new ArrayList<>(); + allTasks.addAll(netTasks); + allTasks.addAll(net2Tasks); + searchAndCompareAsList(queryInList, allTasks); + + // sort + String querySort = String.format("tasks: processId in ('%s', '%s') sort by processId", net.getStringId(), net2.getStringId()); + String querySort2 = String.format("tasks: processId in ('%s', '%s') sort by processId desc", net.getStringId(), net2.getStringId()); + + List asc = new ArrayList<>(); + asc.addAll(netTasks); + asc.addAll(net2Tasks); + List desc = new ArrayList<>(); + desc.addAll(net2Tasks); + desc.addAll(netTasks); + + searchAndCompareAsListInOrder(querySort, asc); + searchAndCompareAsListInOrder(querySort2, desc); + } + + @Test + public void testSearchByLastAssign() { + PetriNet net = importPetriNet("search/search_test.xml"); + IUser user1 = createUser("Name1", "Surname1", "Email1"); + Case case1 = importHelper.createCase("Search Test", net); + Case case2 = importHelper.createCase("Search Test", net); + LocalDateTime before = LocalDateTime.now(); + importHelper.assignTask("Test", case1.getStringId(), user1.transformToLoggedUser()); + importHelper.assignTask("Test", case2.getStringId(), user1.transformToLoggedUser()); + String taskId = case1.getTasks().get(TEST_TRANSITION_ID).getTaskStringId(); + Task task = taskService.findOne(taskId); + String task2Id = case2.getTasks().get(TEST_TRANSITION_ID).getTaskStringId(); + Task task2 = taskService.findOne(task2Id); + + String query = String.format("task: lastAssign eq %s", SearchUtils.toDateTimeString(task.getLastAssigned())); + String queryBefore = String.format("task: lastAssign > %s", SearchUtils.toDateTimeString(before)); + String queryMore = String.format("tasks: lastAssign > %s", SearchUtils.toDateTimeString(before)); + + searchAndCompare(query, task); + searchAndCompare(queryBefore, List.of(task, task2)); + searchAndCompareAsList(queryMore, List.of(task, task2)); + + // in list + String queryInList = String.format("tasks: transitionId eq '%s' and lastAssign in (%s, %s)", TEST_TRANSITION_ID, toDateTimeString(task.getLastAssigned()), toDateTimeString(task2.getLastAssigned())); + searchAndCompareAsList(queryInList, List.of(task, task2)); + + // in range + String queryInRange = String.format("tasks: transitionId eq '%s' and lastAssign in [%s : %s)", TEST_TRANSITION_ID, toDateTimeString(task.getLastAssigned()), toDateTimeString(task2.getLastAssigned())); + searchAndCompareAsList(queryInRange, List.of(task)); + + // sort + String querySort = String.format("tasks: transitionId eq '%s' sort by lastAssign", TEST_TRANSITION_ID); + String querySort2 = String.format("tasks: transitionId eq '%s' sort by lastAssign desc", TEST_TRANSITION_ID); + + searchAndCompareAsListInOrder(querySort, List.of(task, task2)); + searchAndCompareAsListInOrder(querySort2, List.of(task2, task)); + } + + @Test + public void testSearchByLastFinish() { + PetriNet net = importPetriNet("search/search_test.xml"); + IUser user1 = createUser("Name1", "Surname1", "Email1"); + Case case1 = importHelper.createCase("Search Test", net); + Case case2 = importHelper.createCase("Search Test", net); + LocalDateTime before = LocalDateTime.now(); + importHelper.assignTask("Test", case1.getStringId(), user1.transformToLoggedUser()); + importHelper.finishTask("Test", case1.getStringId(), user1.transformToLoggedUser()); + importHelper.assignTask("Test", case2.getStringId(), user1.transformToLoggedUser()); + importHelper.finishTask("Test", case2.getStringId(), user1.transformToLoggedUser()); + String taskId = case1.getTasks().get(TEST_TRANSITION_ID).getTaskStringId(); + Task task = taskService.findOne(taskId); + String task2Id = case2.getTasks().get(TEST_TRANSITION_ID).getTaskStringId(); + Task task2 = taskService.findOne(task2Id); + + String query = String.format("task: lastFinish eq %s", SearchUtils.toDateTimeString(task.getLastFinished())); + String queryBefore = String.format("task: lastFinish gt %s", SearchUtils.toDateTimeString(before)); + String queryMore = String.format("tasks: lastFinish gt %s", SearchUtils.toDateTimeString(before)); + + searchAndCompare(query, task); + searchAndCompare(queryBefore, List.of(task, task2)); + searchAndCompareAsList(queryMore, List.of(task, task2)); + + // in list + String queryInList = String.format("tasks: transitionId eq '%s' and lastFinish in (%s, %s)", TEST_TRANSITION_ID, toDateTimeString(task.getLastFinished()), toDateTimeString(task2.getLastFinished())); + searchAndCompareAsList(queryInList, List.of(task, task2)); + + // in range + String queryInRange = String.format("tasks: transitionId eq '%s' and lastFinish in [%s : %s)", TEST_TRANSITION_ID, toDateTimeString(task.getLastFinished()), toDateTimeString(task2.getLastFinished())); + searchAndCompareAsList(queryInRange, List.of(task)); + + // sort + String querySort = String.format("tasks: transitionId eq '%s' sort by lastFinish", TEST_TRANSITION_ID); + String querySort2 = String.format("tasks: transitionId eq '%s' sort by lastFinish desc", TEST_TRANSITION_ID); + + searchAndCompareAsListInOrder(querySort, List.of(task, task2)); + searchAndCompareAsListInOrder(querySort2, List.of(task2, task)); + } + + @Test + public void testPagination() { + PetriNet net = importPetriNet("search/search_test.xml"); + List tasks = new ArrayList<>(); + for (int i = 0; i < 10; i++) { + Case c = importHelper.createCase("Search Test", net); + tasks.addAll(c.getTasks().values().stream() + .map(taskPair -> taskService.findOne(taskPair.getTaskStringId())) + .sorted(Comparator.comparing(Task::getStringId)) + .collect(Collectors.toList())); + } + + String queryOne = String.format("task: processId eq '%s'", net.getStringId()); + String queryMore = String.format("tasks: processId eq '%s'", net.getStringId()); + String queryMoreCustomPagination = String.format("tasks: processId eq '%s' page 1 size 5", net.getStringId()); + + long count = searchService.count(queryOne); + assert count == 30; + + Object actual = searchService.search(queryOne); + compareById(convertToObject(actual, Task.class), tasks.get(0), Task::getStringId); + + actual = searchService.search(queryMore); + compareById(convertToObjectList(actual, Task.class), tasks.subList(0, 19), Task::getStringId); + + actual = searchService.search(queryMoreCustomPagination); + compareById(convertToObjectList(actual, Task.class), tasks.subList(5, 9), Task::getStringId); + } +} diff --git a/src/test/java/com/netgrif/application/engine/search/SearchUserTest.java b/src/test/java/com/netgrif/application/engine/search/SearchUserTest.java new file mode 100644 index 00000000000..25d1ab86bd5 --- /dev/null +++ b/src/test/java/com/netgrif/application/engine/search/SearchUserTest.java @@ -0,0 +1,209 @@ +package com.netgrif.application.engine.search; + +import com.netgrif.application.engine.TestHelper; +import com.netgrif.application.engine.auth.domain.Authority; +import com.netgrif.application.engine.auth.domain.User; +import com.netgrif.application.engine.petrinet.domain.roles.ProcessRole; +import com.netgrif.application.engine.search.interfaces.ISearchService; +import com.netgrif.application.engine.startup.ImportHelper; +import lombok.extern.slf4j.Slf4j; +import org.junit.jupiter.api.BeforeEach; +import org.junit.jupiter.api.Test; +import org.junit.jupiter.api.extension.ExtendWith; +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.boot.test.context.SpringBootTest; +import org.springframework.test.context.ActiveProfiles; +import org.springframework.test.context.junit.jupiter.SpringExtension; + +import java.util.ArrayList; +import java.util.List; +import java.util.Map; + +import static com.netgrif.application.engine.search.utils.SearchTestUtils.*; + +@Slf4j +@SpringBootTest +@ActiveProfiles({"test"}) +@ExtendWith(SpringExtension.class) +public class SearchUserTest { + + @Autowired + private ImportHelper importHelper; + + @Autowired + private TestHelper testHelper; + + @Autowired + private ISearchService searchService; + + private Map auths; + + @BeforeEach + void setup() { + testHelper.truncateDbs(); + auths = importHelper.createAuthorities(Map.of("user", Authority.user, "admin", Authority.admin)); + } + + private User createUser(String name, String surname, String email, String authority) { + User user = new User(email, "password", name, surname); + Authority[] authorities = new Authority[]{auths.get(authority)}; + ProcessRole[] processRoles = new ProcessRole[]{}; + return (User) importHelper.createUser(user, authorities, processRoles); + } + + private void searchAndCompare(String query, User expected) { + long count = searchService.count(query); + assert count == 1; + + Object actual = searchService.search(query); + compareById(convertToObject(actual, User.class), expected, User::getStringId); + } + + private void searchAndCompare(String query, List expected) { + long count = searchService.count(query); + assert count == expected.size(); + + Object actual = searchService.search(query); + compareById(convertToObject(actual, User.class), expected, User::getStringId); + } + + private void searchAndCompareAsList(String query, List expected) { + long count = searchService.count(query); + assert count == expected.size(); + + Object actual = searchService.search(query); + compareById(convertToObjectList(actual, User.class), expected, User::getStringId); + } + + private void searchAndCompareAsListInOrder(String query, List expected) { + long count = searchService.count(query); + assert count == expected.size(); + + Object actual = searchService.search(query); + compareByIdInOrder(convertToObjectList(actual, User.class), expected, User::getStringId); + } + + @Test + public void testSearchById() { + User user1 = createUser("name1", "surname1", "email1", "user"); + User user2 = createUser("name2", "surname2", "email2", "admin"); + + String query = String.format("user: id eq '%s'", user1.getStringId()); + + searchAndCompare(query, user1); + + // in list + String queryInList = String.format("users: id in ('%s', '%s')", user1.getStringId(), user2.getStringId()); + + searchAndCompareAsList(queryInList, List.of(user1, user2)); + + // sort + String querySort = String.format("users: id in ('%s', '%s') sort by id", user1.getStringId(), user2.getStringId()); + String querySort2 = String.format("users: id in ('%s', '%s') sort by id desc", user1.getStringId(), user2.getStringId()); + + searchAndCompareAsListInOrder(querySort, List.of(user1, user2)); + searchAndCompareAsListInOrder(querySort2, List.of(user2, user1)); + } + + @Test + public void testSearchByEmail() { + User user1 = createUser("name1", "surname1", "email1", "user"); + User user2 = createUser("name2", "surname2", "email2", "admin"); + + String query = String.format("user: email eq '%s'", user1.getEmail()); + + searchAndCompare(query, user1); + + // in list + String queryInList = String.format("users: email in ('%s', '%s')", user1.getEmail(), user2.getEmail()); + + searchAndCompareAsList(queryInList, List.of(user1, user2)); + + // sort + String querySort = String.format("users: id in ('%s', '%s') sort by email", user1.getStringId(), user2.getStringId()); + String querySort2 = String.format("users: id in ('%s', '%s') sort by email desc", user1.getStringId(), user2.getStringId()); + + searchAndCompareAsListInOrder(querySort, List.of(user1, user2)); + searchAndCompareAsListInOrder(querySort2, List.of(user2, user1)); + } + + @Test + public void testSearchByName() { + User user1 = createUser("name1", "surname1", "email1", "user"); + User user2 = createUser("name2", "surname2", "email2", "admin"); + User user3 = createUser("name1", "surname1", "email3", "user"); + + String query = String.format("users: name eq '%s'", user1.getName()); + + searchAndCompareAsList(query, List.of(user1, user3)); + + // in list + String queryInList = String.format("users: name in ('%s', '%s')", user1.getName(), user2.getName()); + + searchAndCompareAsList(queryInList, List.of(user1, user2, user3)); + + // in range + String queryInRange = String.format("users: name in ('%s' : '%s']", user1.getName(), user2.getName()); + + searchAndCompareAsList(queryInRange, List.of(user2)); + + // sort + String querySort = String.format("users: id in ('%s', '%s', '%s') sort by name", user1.getStringId(), user2.getStringId(), user3.getStringId()); + String querySort2 = String.format("users: id in ('%s', '%s', '%s') sort by name desc", user1.getStringId(), user2.getStringId(), user3.getStringId()); + + searchAndCompareAsListInOrder(querySort, List.of(user1, user3, user2)); + searchAndCompareAsListInOrder(querySort2, List.of(user2, user1, user3)); + } + + @Test + public void testSearchBySurname() { + User user1 = createUser("name1", "surname1", "email1", "user"); + User user2 = createUser("name2", "surname2", "email2", "admin"); + User user3 = createUser("name1", "surname1", "email3", "user"); + + String query = String.format("users: surname eq '%s'", user1.getSurname()); + + searchAndCompareAsList(query, List.of(user1, user3)); + + // in list + String queryInList = String.format("users: surname in ('%s', '%s')", user1.getSurname(), user2.getSurname()); + + searchAndCompareAsList(queryInList, List.of(user1, user2, user3)); + + // in range + String queryInRange = String.format("users: surname in ('%s' : '%s']", user1.getSurname(), user2.getSurname()); + + searchAndCompareAsList(queryInRange, List.of(user2)); + + // sort + String querySort = String.format("users: id in ('%s', '%s', '%s') sort by surname", user1.getStringId(), user2.getStringId(), user3.getStringId()); + String querySort2 = String.format("users: id in ('%s', '%s', '%s') sort by surname desc", user1.getStringId(), user2.getStringId(), user3.getStringId()); + + searchAndCompareAsListInOrder(querySort, List.of(user1, user3, user2)); + searchAndCompareAsListInOrder(querySort2, List.of(user2, user1, user3)); + } + + @Test + public void testPagination() { + List users = new ArrayList<>(); + for (int i = 0; i < 50; i++) { + users.add(createUser("name" + i, "surname" + i , "email" + i, "user")); + } + + String queryOne = String.format("user: email contains '%s'", "email"); + String queryMore = String.format("users: email contains '%s'", "email"); + String queryMoreCustomPagination = String.format("users: email contains '%s' page 1 size 5", "email"); + + long count = searchService.count(queryOne); + assert count == 50; + + Object actual = searchService.search(queryOne); + compareById(convertToObject(actual, User.class), users.get(0), User::getStringId); + + actual = searchService.search(queryMore); + compareById(convertToObjectList(actual, User.class), users.subList(0, 19), User::getStringId); + + actual = searchService.search(queryMoreCustomPagination); + compareById(convertToObjectList(actual, User.class), users.subList(5, 9), User::getStringId); + } +} diff --git a/src/test/java/com/netgrif/application/engine/search/utils/MongoDbUtils.java b/src/test/java/com/netgrif/application/engine/search/utils/MongoDbUtils.java new file mode 100644 index 00000000000..e5ca2908ef4 --- /dev/null +++ b/src/test/java/com/netgrif/application/engine/search/utils/MongoDbUtils.java @@ -0,0 +1,18 @@ +package com.netgrif.application.engine.search.utils; + +import com.querydsl.core.types.Predicate; + +import org.bson.Document; +import org.springframework.data.mongodb.core.MongoOperations; +import org.springframework.data.mongodb.repository.support.SpringDataMongodbQuery; + +public class MongoDbUtils extends SpringDataMongodbQuery { + + public MongoDbUtils(MongoOperations operations, Class type) { + super(operations, type, operations.getCollectionName(type)); + } + + public Document convertPredicateToDocument(Predicate predicate) { + return this.createQuery(predicate); + } +} diff --git a/src/test/java/com/netgrif/application/engine/search/utils/SearchTestUtils.java b/src/test/java/com/netgrif/application/engine/search/utils/SearchTestUtils.java new file mode 100644 index 00000000000..93d9d80ecee --- /dev/null +++ b/src/test/java/com/netgrif/application/engine/search/utils/SearchTestUtils.java @@ -0,0 +1,56 @@ +package com.netgrif.application.engine.search.utils; + +import java.util.List; +import java.util.function.Function; +import java.util.stream.Collectors; + +public class SearchTestUtils { + + public static T convertToObject(Object object, Class targetClass) { + assert targetClass.isInstance(object); + return targetClass.cast(object); + } + + public static List convertToObjectList(Object objectList, Class targetClass) { + assert objectList instanceof List; + for (Object object : (List) objectList) { + assert targetClass.isInstance(object); + } + + return (List) objectList; + } + + public static void compareById(T actual, T expected, Function getId) { + assert getId.apply(actual).equals(getId.apply(expected)); + } + + public static void compareById(T actual, List expected, Function getId) { + List expectedIds = expected.stream() + .map(getId) + .collect(Collectors.toList()); + + assert expectedIds.contains(getId.apply(actual)); + } + + public static void compareById(List actual, List expected, Function getId) { + List actualIds = actual.stream() + .map(getId) + .collect(Collectors.toList()); + List expectedIds = expected.stream() + .map(getId) + .collect(Collectors.toList()); + + assert actualIds.containsAll(expectedIds); + } + + public static void compareByIdInOrder(List actual, List expected, Function getId) { + List actualIds = actual.stream() + .map(getId) + .collect(Collectors.toList()); + List expectedIds = expected.stream() + .map(getId) + .collect(Collectors.toList()); + + assert actualIds.equals(expectedIds); + } +} diff --git a/src/test/resources/petriNets/search/search_test.xml b/src/test/resources/petriNets/search/search_test.xml new file mode 100644 index 00000000000..f28e3385c55 --- /dev/null +++ b/src/test/resources/petriNets/search/search_test.xml @@ -0,0 +1,220 @@ + + search_test + ST1 + Search Test + check_circle + true + true + false + + text_immediate + + <init>test</init> + </data> + <data type="number" immediate="true"> + <id>number_immediate</id> + <title/> + <init>54</init> + </data> + <data type="enumeration_map" immediate="true"> + <id>enumeration_map_immediate</id> + <title/> + <options> + <option key="key1">value1</option> + <option key="key2">value2</option> + <option key="key3">value3</option> + </options> + <init>key2</init> + </data> + <data type="multichoice_map" immediate="true"> + <id>multichoice_map_immediate</id> + <title/> + <options> + <option key="key1">value1</option> + <option key="key2">value2</option> + <option key="key3">value3</option> + </options> + <inits> + <init>key1</init> + <init>key2</init> + </inits> + </data> + <data type="boolean" immediate="true"> + <id>boolean_immediate</id> + <title/> + <init>true</init> + </data> + <data type="date" immediate="true"> + <id>date_immediate</id> + <title/> + <init dynamic="true">java.time.LocalDate.now()</init> + </data> + <data type="dateTime" immediate="true"> + <id>date_time_immediate</id> + <title/> + <init dynamic="true">java.time.LocalDateTime.now()</init> + </data> + <transition> + <id>search_test_t1</id> + <x>260</x> + <y>100</y> + <label>Test</label> + <dataGroup> + <id>search_test_t1_0</id> + <cols>4</cols> + <layout>grid</layout> + <dataRef> + <id>text_immediate</id> + <logic> + <behavior>editable</behavior> + </logic> + <layout> + <x>0</x> + <y>0</y> + <rows>1</rows> + <cols>4</cols> + <template>material</template> + <appearance>outline</appearance> + </layout> + </dataRef> + <dataRef> + <id>number_immediate</id> + <logic> + <behavior>editable</behavior> + </logic> + <layout> + <x>0</x> + <y>1</y> + <rows>1</rows> + <cols>4</cols> + <template>material</template> + <appearance>outline</appearance> + </layout> + </dataRef> + <dataRef> + <id>enumeration_map_immediate</id> + <logic> + <behavior>editable</behavior> + </logic> + <layout> + <x>0</x> + <y>2</y> + <rows>1</rows> + <cols>4</cols> + <template>material</template> + <appearance>outline</appearance> + </layout> + </dataRef> + <dataRef> + <id>multichoice_map_immediate</id> + <logic> + <behavior>editable</behavior> + </logic> + <layout> + <x>0</x> + <y>3</y> + <rows>1</rows> + <cols>4</cols> + <template>material</template> + <appearance>outline</appearance> + </layout> + </dataRef> + <dataRef> + <id>boolean_immediate</id> + <logic> + <behavior>editable</behavior> + </logic> + <layout> + <x>0</x> + <y>4</y> + <rows>1</rows> + <cols>4</cols> + <template>material</template> + <appearance>outline</appearance> + </layout> + </dataRef> + <dataRef> + <id>date_immediate</id> + <logic> + <behavior>editable</behavior> + </logic> + <layout> + <x>0</x> + <y>5</y> + <rows>1</rows> + <cols>4</cols> + <template>material</template> + <appearance>outline</appearance> + </layout> + </dataRef> + <dataRef> + <id>date_time_immediate</id> + <logic> + <behavior>editable</behavior> + </logic> + <layout> + <x>0</x> + <y>6</y> + <rows>1</rows> + <cols>4</cols> + <template>material</template> + <appearance>outline</appearance> + </layout> + </dataRef> + </dataGroup> + <event type="assign"> + <id>search_test_t1_assign</id> + </event> + <event type="finish"> + <id>search_test_t1_finish</id> + </event> + <event type="cancel"> + <id>search_test_t1_cancel</id> + </event> + <event type="delegate"> + <id>search_test_t1_delegate</id> + </event> + </transition> + <transition> + <id>search_test_t2</id> + <x>260</x> + <y>180</y> + <label>Test T2</label> + </transition> + <place> + <id>p1</id> + <x>180</x> + <y>100</y> + <tokens>1</tokens> + <static>false</static> + </place> + <place> + <id>p2</id> + <x>340</x> + <y>100</y> + <tokens>0</tokens> + <static>false</static> + </place> + <arc> + <id>a1</id> + <type>regular</type> + <sourceId>p1</sourceId> + <destinationId>search_test_t1</destinationId> + <multiplicity>1</multiplicity> + </arc> + <arc> + <id>a2</id> + <type>regular</type> + <sourceId>search_test_t1</sourceId> + <destinationId>p2</destinationId> + <multiplicity>1</multiplicity> + </arc> + <arc> + <id>a3</id> + <type>regular</type> + <sourceId>p1</sourceId> + <destinationId>search_test_t2</destinationId> + <multiplicity>1</multiplicity> + </arc> +</document> \ No newline at end of file diff --git a/src/test/resources/petriNets/search/search_test2.xml b/src/test/resources/petriNets/search/search_test2.xml new file mode 100644 index 00000000000..a2d9ed2def1 --- /dev/null +++ b/src/test/resources/petriNets/search/search_test2.xml @@ -0,0 +1,207 @@ +<document xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" + xsi:noNamespaceSchemaLocation="https://petriflow.com/petriflow.schema.xsd"> + <id>search_test2</id> + <initials>ST2</initials> + <title>Search Test 2 + check_circle + true + true + false + + text_immediate + + <init>test</init> + </data> + <data type="number" immediate="true"> + <id>number_immediate</id> + <title/> + <init>54</init> + </data> + <data type="enumeration_map" immediate="true"> + <id>enumeration_map_immediate</id> + <title/> + <options> + <option key="key1">value1</option> + <option key="key2">value2</option> + <option key="key3">value3</option> + </options> + <init>key2</init> + </data> + <data type="multichoice_map" immediate="true"> + <id>multichoice_map_immediate</id> + <title/> + <options> + <option key="key1">value1</option> + <option key="key2">value2</option> + <option key="key3">value3</option> + </options> + <inits> + <init>key1</init> + <init>key2</init> + </inits> + </data> + <data type="boolean" immediate="true"> + <id>boolean_immediate</id> + <title/> + <init>true</init> + </data> + <data type="date" immediate="true"> + <id>date_immediate</id> + <title/> + <init dynamic="true">java.time.LocalDate.now()</init> + </data> + <data type="dateTime" immediate="true"> + <id>date_time_immediate</id> + <title/> + <init dynamic="true">java.time.LocalDateTime.now()</init> + </data> + <transition> + <id>search_test_t1</id> + <x>260</x> + <y>100</y> + <label>Test2</label> + <dataGroup> + <id>search_test_t1_0</id> + <cols>4</cols> + <layout>grid</layout> + <dataRef> + <id>text_immediate</id> + <logic> + <behavior>editable</behavior> + </logic> + <layout> + <x>0</x> + <y>0</y> + <rows>1</rows> + <cols>4</cols> + <template>material</template> + <appearance>outline</appearance> + </layout> + </dataRef> + <dataRef> + <id>number_immediate</id> + <logic> + <behavior>editable</behavior> + </logic> + <layout> + <x>0</x> + <y>1</y> + <rows>1</rows> + <cols>4</cols> + <template>material</template> + <appearance>outline</appearance> + </layout> + </dataRef> + <dataRef> + <id>enumeration_map_immediate</id> + <logic> + <behavior>editable</behavior> + </logic> + <layout> + <x>0</x> + <y>2</y> + <rows>1</rows> + <cols>4</cols> + <template>material</template> + <appearance>outline</appearance> + </layout> + </dataRef> + <dataRef> + <id>multichoice_map_immediate</id> + <logic> + <behavior>editable</behavior> + </logic> + <layout> + <x>0</x> + <y>3</y> + <rows>1</rows> + <cols>4</cols> + <template>material</template> + <appearance>outline</appearance> + </layout> + </dataRef> + <dataRef> + <id>boolean_immediate</id> + <logic> + <behavior>editable</behavior> + </logic> + <layout> + <x>0</x> + <y>4</y> + <rows>1</rows> + <cols>4</cols> + <template>material</template> + <appearance>outline</appearance> + </layout> + </dataRef> + <dataRef> + <id>date_immediate</id> + <logic> + <behavior>editable</behavior> + </logic> + <layout> + <x>0</x> + <y>5</y> + <rows>1</rows> + <cols>4</cols> + <template>material</template> + <appearance>outline</appearance> + </layout> + </dataRef> + <dataRef> + <id>date_time_immediate</id> + <logic> + <behavior>editable</behavior> + </logic> + <layout> + <x>0</x> + <y>6</y> + <rows>1</rows> + <cols>4</cols> + <template>material</template> + <appearance>outline</appearance> + </layout> + </dataRef> + </dataGroup> + <event type="assign"> + <id>search_test_t1_asign</id> + </event> + <event type="finish"> + <id>search_test_t1_fiish</id> + </event> + <event type="cancel"> + <id>search_test_t1_cacel</id> + </event> + <event type="delegate"> + <id>search_test_t1_deleate</id> + </event> + </transition> + <place> + <id>p1</id> + <x>180</x> + <y>100</y> + <tokens>1</tokens> + <static>false</static> + </place> + <place> + <id>p2</id> + <x>340</x> + <y>100</y> + <tokens>0</tokens> + <static>false</static> + </place> + <arc> + <id>a1</id> + <type>regular</type> + <sourceId>p1</sourceId> + <destinationId>search_test_t1</destinationId> + <multiplicity>1</multiplicity> + </arc> + <arc> + <id>a2</id> + <type>regular</type> + <sourceId>search_test_t1</sourceId> + <destinationId>p2</destinationId> + <multiplicity>1</multiplicity> + </arc> +</document> \ No newline at end of file diff --git a/src/test/resources/petriNets/search/search_test3.xml b/src/test/resources/petriNets/search/search_test3.xml new file mode 100644 index 00000000000..9621b6295d1 --- /dev/null +++ b/src/test/resources/petriNets/search/search_test3.xml @@ -0,0 +1,207 @@ +<document xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" + xsi:noNamespaceSchemaLocation="https://petriflow.com/petriflow.schema.xsd"> + <id>search_test3</id> + <initials>ST3</initials> + <title>Search Test 3 + check_circle + true + true + false + + text_immediate + + <init>test</init> + </data> + <data type="number" immediate="true"> + <id>number_immediate</id> + <title/> + <init>54</init> + </data> + <data type="enumeration_map" immediate="true"> + <id>enumeration_map_immediate</id> + <title/> + <options> + <option key="key1">value1</option> + <option key="key2">value2</option> + <option key="key3">value3</option> + </options> + <init>key2</init> + </data> + <data type="multichoice_map" immediate="true"> + <id>multichoice_map_immediate</id> + <title/> + <options> + <option key="key1">value1</option> + <option key="key2">value2</option> + <option key="key3">value3</option> + </options> + <inits> + <init>key1</init> + <init>key2</init> + </inits> + </data> + <data type="boolean" immediate="true"> + <id>boolean_immediate</id> + <title/> + <init>true</init> + </data> + <data type="date" immediate="true"> + <id>date_immediate</id> + <title/> + <init dynamic="true">java.time.LocalDate.now()</init> + </data> + <data type="dateTime" immediate="true"> + <id>date_time_immediate</id> + <title/> + <init dynamic="true">java.time.LocalDateTime.now()</init> + </data> + <transition> + <id>search_test_t1</id> + <x>260</x> + <y>100</y> + <label>Test3</label> + <dataGroup> + <id>search_test_t1_0</id> + <cols>4</cols> + <layout>grid</layout> + <dataRef> + <id>text_immediate</id> + <logic> + <behavior>editable</behavior> + </logic> + <layout> + <x>0</x> + <y>0</y> + <rows>1</rows> + <cols>4</cols> + <template>material</template> + <appearance>outline</appearance> + </layout> + </dataRef> + <dataRef> + <id>number_immediate</id> + <logic> + <behavior>editable</behavior> + </logic> + <layout> + <x>0</x> + <y>1</y> + <rows>1</rows> + <cols>4</cols> + <template>material</template> + <appearance>outline</appearance> + </layout> + </dataRef> + <dataRef> + <id>enumeration_map_immediate</id> + <logic> + <behavior>editable</behavior> + </logic> + <layout> + <x>0</x> + <y>2</y> + <rows>1</rows> + <cols>4</cols> + <template>material</template> + <appearance>outline</appearance> + </layout> + </dataRef> + <dataRef> + <id>multichoice_map_immediate</id> + <logic> + <behavior>editable</behavior> + </logic> + <layout> + <x>0</x> + <y>3</y> + <rows>1</rows> + <cols>4</cols> + <template>material</template> + <appearance>outline</appearance> + </layout> + </dataRef> + <dataRef> + <id>boolean_immediate</id> + <logic> + <behavior>editable</behavior> + </logic> + <layout> + <x>0</x> + <y>4</y> + <rows>1</rows> + <cols>4</cols> + <template>material</template> + <appearance>outline</appearance> + </layout> + </dataRef> + <dataRef> + <id>date_immediate</id> + <logic> + <behavior>editable</behavior> + </logic> + <layout> + <x>0</x> + <y>5</y> + <rows>1</rows> + <cols>4</cols> + <template>material</template> + <appearance>outline</appearance> + </layout> + </dataRef> + <dataRef> + <id>date_time_immediate</id> + <logic> + <behavior>editable</behavior> + </logic> + <layout> + <x>0</x> + <y>6</y> + <rows>1</rows> + <cols>4</cols> + <template>material</template> + <appearance>outline</appearance> + </layout> + </dataRef> + </dataGroup> + <event type="assign"> + <id>search_test_t1_asign</id> + </event> + <event type="finish"> + <id>search_test_t1_fiish</id> + </event> + <event type="cancel"> + <id>search_test_t1_cacel</id> + </event> + <event type="delegate"> + <id>search_test_t1_deleate</id> + </event> + </transition> + <place> + <id>p1</id> + <x>180</x> + <y>100</y> + <tokens>1</tokens> + <static>false</static> + </place> + <place> + <id>p2</id> + <x>340</x> + <y>100</y> + <tokens>0</tokens> + <static>false</static> + </place> + <arc> + <id>a1</id> + <type>regular</type> + <sourceId>p1</sourceId> + <destinationId>search_test_t1</destinationId> + <multiplicity>1</multiplicity> + </arc> + <arc> + <id>a2</id> + <type>regular</type> + <sourceId>search_test_t1</sourceId> + <destinationId>p2</destinationId> + <multiplicity>1</multiplicity> + </arc> +</document> \ No newline at end of file