From 5a9b88ab4f2ba90b66df9edc7dfdec81de6cb544 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Juraj=20Ma=C5=BE=C3=A1ri?= Date: Fri, 29 May 2026 15:21:02 +0200 Subject: [PATCH 01/17] [NAE-2443] - PFQL support - update ImportHelper to support params map - add PFQL from v8 task NAE-1997 - add PFQL tests - add antlr4 runtime and build plugin to pom --- pom.xml | 23 + .../engine/startup/ImportHelper.groovy | 8 +- .../pfql/domain/enums/ComparisonType.java | 11 + .../engine/pfql/domain/enums/QueryType.java | 8 + .../engine/pfql/service/ISearchService.java | 11 + .../pfql/service/QueryLangErrorListener.java | 36 + .../pfql/service/QueryLangEvaluator.java | 1423 ++++++++++++ .../service/QueryLangExplainEvaluator.java | 506 +++++ .../engine/pfql/service/SearchService.java | 158 ++ .../pfql/service/utils/QueryLangTreeNode.java | 43 + .../pfql/service/utils/SearchUtils.java | 425 ++++ .../engine/pfql/QueryLangTest.java | 1921 +++++++++++++++++ .../engine/pfql/utils/MongoDbUtils.java | 18 + .../engine/pfql/utils/SearchTestUtils.java | 56 + src/test/resources/petriNets/pfql.xml | 97 + 15 files changed, 4740 insertions(+), 4 deletions(-) create mode 100644 src/main/java/com/netgrif/application/engine/pfql/domain/enums/ComparisonType.java create mode 100644 src/main/java/com/netgrif/application/engine/pfql/domain/enums/QueryType.java create mode 100644 src/main/java/com/netgrif/application/engine/pfql/service/ISearchService.java create mode 100644 src/main/java/com/netgrif/application/engine/pfql/service/QueryLangErrorListener.java create mode 100644 src/main/java/com/netgrif/application/engine/pfql/service/QueryLangEvaluator.java create mode 100644 src/main/java/com/netgrif/application/engine/pfql/service/QueryLangExplainEvaluator.java create mode 100644 src/main/java/com/netgrif/application/engine/pfql/service/SearchService.java create mode 100644 src/main/java/com/netgrif/application/engine/pfql/service/utils/QueryLangTreeNode.java create mode 100644 src/main/java/com/netgrif/application/engine/pfql/service/utils/SearchUtils.java create mode 100644 src/test/groovy/com/netgrif/application/engine/pfql/QueryLangTest.java create mode 100644 src/test/groovy/com/netgrif/application/engine/pfql/utils/MongoDbUtils.java create mode 100644 src/test/groovy/com/netgrif/application/engine/pfql/utils/SearchTestUtils.java create mode 100644 src/test/resources/petriNets/pfql.xml diff --git a/pom.xml b/pom.xml index 5b79a2f6768..20cc96301cd 100644 --- a/pom.xml +++ b/pom.xml @@ -64,6 +64,7 @@ 7.70.0.Final netgrif-oss https://sonarcloud.io + 4.13.1 @@ -530,6 +531,12 @@ minio 8.5.12 + + + org.antlr + antlr4-runtime + ${antlr4.version} + @@ -795,6 +802,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/startup/ImportHelper.groovy b/src/main/groovy/com/netgrif/application/engine/startup/ImportHelper.groovy index 295c23f9f9f..ba5cde7d804 100644 --- a/src/main/groovy/com/netgrif/application/engine/startup/ImportHelper.groovy +++ b/src/main/groovy/com/netgrif/application/engine/startup/ImportHelper.groovy @@ -180,12 +180,12 @@ class ImportHelper { return user } - Case createCase(String title, PetriNet net, LoggedUser user) { - return workflowService.createCase(net.getStringId(), title, "", user).getCase() + Case createCase(String title, PetriNet net, LoggedUser user, Map params = [:]) { + return workflowService.createCase(net.getStringId(), title, "", user, params).getCase() } - Case createCase(String title, PetriNet net) { - return createCase(title, net, userService.getSystem().transformToLoggedUser()) + Case createCase(String title, PetriNet net, Map params = [:]) { + return createCase(title, net, userService.getSystem().transformToLoggedUser(), params) } Case createCaseAsSuper(String title, PetriNet net) { diff --git a/src/main/java/com/netgrif/application/engine/pfql/domain/enums/ComparisonType.java b/src/main/java/com/netgrif/application/engine/pfql/domain/enums/ComparisonType.java new file mode 100644 index 00000000000..acb085c3933 --- /dev/null +++ b/src/main/java/com/netgrif/application/engine/pfql/domain/enums/ComparisonType.java @@ -0,0 +1,11 @@ +package com.netgrif.application.engine.pfql.domain.enums; + +public enum ComparisonType { + ID, + STRING, + NUMBER, + DATE, + DATETIME, + BOOLEAN, + OPTIONS +} diff --git a/src/main/java/com/netgrif/application/engine/pfql/domain/enums/QueryType.java b/src/main/java/com/netgrif/application/engine/pfql/domain/enums/QueryType.java new file mode 100644 index 00000000000..50ad695c128 --- /dev/null +++ b/src/main/java/com/netgrif/application/engine/pfql/domain/enums/QueryType.java @@ -0,0 +1,8 @@ +package com.netgrif.application.engine.pfql.domain.enums; + +public enum QueryType { + PROCESS, + CASE, + TASK, + USER +} diff --git a/src/main/java/com/netgrif/application/engine/pfql/service/ISearchService.java b/src/main/java/com/netgrif/application/engine/pfql/service/ISearchService.java new file mode 100644 index 00000000000..6de405b1737 --- /dev/null +++ b/src/main/java/com/netgrif/application/engine/pfql/service/ISearchService.java @@ -0,0 +1,11 @@ +package com.netgrif.application.engine.pfql.service; + +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/pfql/service/QueryLangErrorListener.java b/src/main/java/com/netgrif/application/engine/pfql/service/QueryLangErrorListener.java new file mode 100644 index 00000000000..bfb03e94102 --- /dev/null +++ b/src/main/java/com/netgrif/application/engine/pfql/service/QueryLangErrorListener.java @@ -0,0 +1,36 @@ +package com.netgrif.application.engine.pfql.service; + +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/pfql/service/QueryLangEvaluator.java b/src/main/java/com/netgrif/application/engine/pfql/service/QueryLangEvaluator.java new file mode 100644 index 00000000000..c920cd766bc --- /dev/null +++ b/src/main/java/com/netgrif/application/engine/pfql/service/QueryLangEvaluator.java @@ -0,0 +1,1423 @@ +package com.netgrif.application.engine.pfql.service; + +import com.netgrif.application.engine.auth.domain.QUser; +import com.netgrif.application.engine.petrinet.domain.QPetriNet; +import com.netgrif.application.engine.pfql.domain.antlr4.QueryLangBaseListener; +import com.netgrif.application.engine.pfql.domain.antlr4.QueryLangParser; +import com.netgrif.application.engine.pfql.domain.enums.ComparisonType; +import com.netgrif.application.engine.pfql.domain.enums.QueryType; +import com.netgrif.application.engine.workflow.domain.QCase; +import com.netgrif.application.engine.workflow.domain.QTask; +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.ArrayList; +import java.util.List; +import java.util.Objects; +import java.util.stream.Collectors; + +import static com.netgrif.application.engine.pfql.service.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/pfql/service/QueryLangExplainEvaluator.java b/src/main/java/com/netgrif/application/engine/pfql/service/QueryLangExplainEvaluator.java new file mode 100644 index 00000000000..18d07849988 --- /dev/null +++ b/src/main/java/com/netgrif/application/engine/pfql/service/QueryLangExplainEvaluator.java @@ -0,0 +1,506 @@ +package com.netgrif.application.engine.pfql.service; + +import com.netgrif.application.engine.pfql.domain.antlr4.QueryLangBaseListener; +import com.netgrif.application.engine.pfql.domain.antlr4.QueryLangParser; +import com.netgrif.application.engine.pfql.domain.enums.QueryType; +import com.netgrif.application.engine.pfql.service.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.pfql.service.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/pfql/service/SearchService.java b/src/main/java/com/netgrif/application/engine/pfql/service/SearchService.java new file mode 100644 index 00000000000..303ef09672f --- /dev/null +++ b/src/main/java/com/netgrif/application/engine/pfql/service/SearchService.java @@ -0,0 +1,158 @@ +package com.netgrif.application.engine.pfql.service; + +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.pfql.service.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.pfql.domain.enums.QueryType.*; +import static com.netgrif.application.engine.pfql.service.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/pfql/service/utils/QueryLangTreeNode.java b/src/main/java/com/netgrif/application/engine/pfql/service/utils/QueryLangTreeNode.java new file mode 100644 index 00000000000..465073d2186 --- /dev/null +++ b/src/main/java/com/netgrif/application/engine/pfql/service/utils/QueryLangTreeNode.java @@ -0,0 +1,43 @@ +package com.netgrif.application.engine.pfql.service.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/pfql/service/utils/SearchUtils.java b/src/main/java/com/netgrif/application/engine/pfql/service/utils/SearchUtils.java new file mode 100644 index 00000000000..7a36dda75b0 --- /dev/null +++ b/src/main/java/com/netgrif/application/engine/pfql/service/utils/SearchUtils.java @@ -0,0 +1,425 @@ +package com.netgrif.application.engine.pfql.service.utils; + +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.pfql.domain.antlr4.QueryLangBaseListener; +import com.netgrif.application.engine.pfql.domain.antlr4.QueryLangLexer; +import com.netgrif.application.engine.pfql.domain.antlr4.QueryLangParser; +import com.netgrif.application.engine.pfql.domain.enums.ComparisonType; +import com.netgrif.application.engine.pfql.service.QueryLangErrorListener; +import com.netgrif.application.engine.pfql.service.QueryLangEvaluator; +import com.netgrif.application.engine.pfql.service.QueryLangExplainEvaluator; +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/groovy/com/netgrif/application/engine/pfql/QueryLangTest.java b/src/test/groovy/com/netgrif/application/engine/pfql/QueryLangTest.java new file mode 100644 index 00000000000..e575d2f4c59 --- /dev/null +++ b/src/test/groovy/com/netgrif/application/engine/pfql/QueryLangTest.java @@ -0,0 +1,1921 @@ +package com.netgrif.application.engine.pfql; + +import com.netgrif.application.engine.TestHelper; +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.pfql.service.ISearchService; +import com.netgrif.application.engine.pfql.utils.MongoDbUtils; +import com.netgrif.application.engine.startup.ImportHelper; +import com.netgrif.application.engine.workflow.domain.Case; +import com.netgrif.application.engine.workflow.domain.QCase; +import com.netgrif.application.engine.workflow.domain.QTask; +import com.netgrif.application.engine.workflow.domain.Task; +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.HashMap; +import java.util.List; +import java.util.Map; +import java.util.Optional; + +import static com.netgrif.application.engine.pfql.service.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; + @Autowired + ISearchService searchService; + @Autowired + ImportHelper helper; + @Autowired + TestHelper testHelper; + + @Test + public void testSearchService() { + testHelper.truncateDbs(); + + Optional optionalPetriNet = helper.createNet("/pfql.xml"); + if (optionalPetriNet.isEmpty()) { + throw new RuntimeException("query_test.xml net not created"); + } + + Object process = searchService.search("process: identifier == 'query_test'"); + if (!(process instanceof PetriNet)) { + throw new RuntimeException("Process not found"); + } + PetriNet net = (PetriNet) process; + for (int i = 0; i < 10; i++) { + Map params = new HashMap<>(); + params.put("id", ""+i); + helper.createCase(String.format("Test %02d", i), net, params); + } + + Object cases = searchService.search("cases: processIdentifier eq 'query_test' page 1 size 5 sort by title desc"); + log.info("Cases: {}", cases); + + Object case_3 = searchService.search("case: processIdentifier eq 'query_test' and data.number_0.value == 3"); + log.info("Case with number_0 == 3: {}", ((Case)case_3).getTitle()); + Object case_4 = searchService.search("case: processIdentifier eq 'query_test' and data.text_0.value == '4'"); + log.info("Case with text_0 == '4': {}", ((Case)case_4).getTitle()); + Object case_5 = searchService.search("case: processIdentifier eq 'query_test' and data.boolean_0.value == true"); + log.info("Case with boolean_0 == true: {}", ((Case)case_5).getTitle()); + + cases = searchService.search("cases: processIdentifier eq 'query_test' and data.boolean_0.value == true"); + assert ((List)cases).size() == 5; + + // TODO: release/8.0.0 add 'neq'/'!=', simplify 'not' requires () + cases = searchService.search("cases: processIdentifier eq 'query_test' and data.boolean_0.value == true and not (data.text_0.value == '4')"); + assert ((List)cases).size() == 4; + } + + @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 +// TODO: fix +// 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 +// TODO: fix +// 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 +// TODO: fix +// 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/groovy/com/netgrif/application/engine/pfql/utils/MongoDbUtils.java b/src/test/groovy/com/netgrif/application/engine/pfql/utils/MongoDbUtils.java new file mode 100644 index 00000000000..5fc8d04ffe5 --- /dev/null +++ b/src/test/groovy/com/netgrif/application/engine/pfql/utils/MongoDbUtils.java @@ -0,0 +1,18 @@ +package com.netgrif.application.engine.pfql.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/groovy/com/netgrif/application/engine/pfql/utils/SearchTestUtils.java b/src/test/groovy/com/netgrif/application/engine/pfql/utils/SearchTestUtils.java new file mode 100644 index 00000000000..8664c8a8d25 --- /dev/null +++ b/src/test/groovy/com/netgrif/application/engine/pfql/utils/SearchTestUtils.java @@ -0,0 +1,56 @@ +package com.netgrif.application.engine.pfql.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/pfql.xml b/src/test/resources/petriNets/pfql.xml new file mode 100644 index 00000000000..1dd151d8199 --- /dev/null +++ b/src/test/resources/petriNets/pfql.xml @@ -0,0 +1,97 @@ + + query_test + 1.0.0 + NEW + New Model + device_hub + true + true + false + + boolean_0 + Boolean + (params["id"] as int) % 2 == 0 + + + dateTime_0 + Date Time + java.time.LocalDateTime.now().plusYears(params["id"] as int) + + + number_0 + Number + params["id"] as int + + + text_0 + Text + params["id"] + + + t1 + 176 + 112 + + + t1_0 + 4 + grid + + text_0 + + editable + + + 0 + 0 + 1 + 2 + + outline + + + + number_0 + + editable + + + 2 + 0 + 1 + 2 + + outline + + + + boolean_0 + + editable + + + 0 + 1 + 1 + 2 + + outline + + + + dateTime_0 + + editable + + + 2 + 1 + 1 + 2 + + outline + + + + + \ No newline at end of file From 0bb0ce0ccd466e71e28619f743f2406b5055779f Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Juraj=20Ma=C5=BE=C3=A1ri?= Date: Fri, 29 May 2026 15:45:36 +0200 Subject: [PATCH 02/17] [NAE-2443] - PFQL support - add missing PFQL grammar --- .../engine/pfql/domain/antlr4/QueryLang.g4 | 360 ++++++++++++++++++ 1 file changed, 360 insertions(+) create mode 100644 src/main/java/com/netgrif/application/engine/pfql/domain/antlr4/QueryLang.g4 diff --git a/src/main/java/com/netgrif/application/engine/pfql/domain/antlr4/QueryLang.g4 b/src/main/java/com/netgrif/application/engine/pfql/domain/antlr4/QueryLang.g4 new file mode 100644 index 00000000000..88c455c6c03 --- /dev/null +++ b/src/main/java/com/netgrif/application/engine/pfql/domain/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]; From c2a274c1a6b525f2096d3c12406220f402ef13ff Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Juraj=20Ma=C5=BE=C3=A1ri?= Date: Tue, 2 Jun 2026 14:14:12 +0200 Subject: [PATCH 03/17] [NAE-2443] - PFQL support - add separate searchCase and searchCases functions --- .../engine/pfql/service/SearchService.java | 86 ++++++++++++------- 1 file changed, 55 insertions(+), 31 deletions(-) diff --git a/src/main/java/com/netgrif/application/engine/pfql/service/SearchService.java b/src/main/java/com/netgrif/application/engine/pfql/service/SearchService.java index 303ef09672f..9742d5f4453 100644 --- a/src/main/java/com/netgrif/application/engine/pfql/service/SearchService.java +++ b/src/main/java/com/netgrif/application/engine/pfql/service/SearchService.java @@ -9,6 +9,7 @@ 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.netgrif.application.engine.workflow.service.interfaces.IWorkflowService; import com.querydsl.core.types.Predicate; import lombok.RequiredArgsConstructor; import lombok.extern.slf4j.Slf4j; @@ -19,7 +20,6 @@ import java.util.List; -import static com.netgrif.application.engine.pfql.domain.enums.QueryType.*; import static com.netgrif.application.engine.pfql.service.utils.SearchUtils.evaluateQuery; @Slf4j @@ -28,47 +28,44 @@ public class SearchService implements ISearchService { private final PetriNetRepository petriNetRepository; - private final IElasticCaseService elasticCaseService; - + private final IWorkflowService workflowService; 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 - ); + @Override + public String explainQuery(String input) { + return SearchUtils.explainQuery(input); } - 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(); - } + public List searchCases(String query) { + QueryLangEvaluator evaluator = evaluateQuery(query); + Pageable pageable = evaluator.getPageable(); - private boolean existsCasesElastic(String elasticQuery) { - return countCasesElastic(elasticQuery) > 0; + if (!evaluator.getSearchWithElastic()) { + Predicate predicate = evaluator.getFullMongoQuery(); + return caseRepository.findAll(predicate, pageable).getContent(); + } + + String elasticQuery = evaluator.getFullElasticQuery(); + return findCasesElastic(elasticQuery, pageable); } - @Override - public String explainQuery(String input) { - return SearchUtils.explainQuery(input); + public Case searchCase(String query) { + QueryLangEvaluator evaluator = evaluateQuery(query); + Pageable pageable = evaluator.getPageable(); + + if (!evaluator.getSearchWithElastic()) { + Predicate predicate = evaluator.getFullMongoQuery(); + return caseRepository.findAll(predicate, PageRequest.of(0, 1)) + .getContent().stream().findFirst().orElse(null); + } + + String elasticQuery = evaluator.getFullElasticQuery(); + List cases = findCasesElastic(elasticQuery, pageable); + return cases.stream().findFirst().orElse(null); } @Override @@ -155,4 +152,31 @@ public boolean exists(String input) { } return false; } + + 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; + } } From 3826be0fe90618372468059dce4571378f93f7d8 Mon Sep 17 00:00:00 2001 From: chvostek Date: Thu, 4 Jun 2026 12:00:08 +0200 Subject: [PATCH 04/17] [NAE-2443] PFQL support - start code encapsulation of SearchService - add prefix Legacy to old search services --- .../engine/elastic/web/ElasticController.java | 4 +- .../petrinet/service/PetriNetService.java | 31 +++ .../service/interfaces/IPetriNetService.java | 7 + .../pfql/service/IResourceSearchService.java | 38 +++ .../engine/pfql/service/ISearchService.java | 2 +- .../pfql/service/QueryLangEvaluator.java | 3 + .../engine/pfql/service/SearchService.java | 207 +++++---------- .../caseresource/CaseSearchService.java | 236 ++++++++++++++++++ .../processresource/ProcessSearchService.java | 216 ++++++++++++++++ .../taskresource/TaskSearchService.java | 69 +++++ .../userresource/UserSearchService.java | 63 +++++ ...vice.java => LegacyCaseSearchService.java} | 6 +- ...vice.java => LegacyTaskSearchService.java} | 2 +- .../engine/workflow/service/TaskService.java | 2 +- .../workflow/service/WorkflowService.java | 24 +- .../service/interfaces/IWorkflowService.java | 4 + .../engine/workflow/CaseSearchTest.groovy | 8 +- .../engine/workflow/TaskControllerTest.groovy | 4 +- 18 files changed, 763 insertions(+), 163 deletions(-) create mode 100644 src/main/java/com/netgrif/application/engine/pfql/service/IResourceSearchService.java create mode 100644 src/main/java/com/netgrif/application/engine/pfql/service/caseresource/CaseSearchService.java create mode 100644 src/main/java/com/netgrif/application/engine/pfql/service/processresource/ProcessSearchService.java create mode 100644 src/main/java/com/netgrif/application/engine/pfql/service/taskresource/TaskSearchService.java create mode 100644 src/main/java/com/netgrif/application/engine/pfql/service/userresource/UserSearchService.java rename src/main/java/com/netgrif/application/engine/workflow/service/{CaseSearchService.java => LegacyCaseSearchService.java} (98%) rename src/main/java/com/netgrif/application/engine/workflow/service/{TaskSearchService.java => LegacyTaskSearchService.java} (99%) diff --git a/src/main/java/com/netgrif/application/engine/elastic/web/ElasticController.java b/src/main/java/com/netgrif/application/engine/elastic/web/ElasticController.java index c50c37df036..41cdd8a6c80 100644 --- a/src/main/java/com/netgrif/application/engine/elastic/web/ElasticController.java +++ b/src/main/java/com/netgrif/application/engine/elastic/web/ElasticController.java @@ -4,7 +4,7 @@ import com.netgrif.application.engine.elastic.service.ReindexingTask; import com.netgrif.application.engine.elastic.service.interfaces.IElasticIndexService; import com.netgrif.application.engine.elastic.web.requestbodies.IndexParams; -import com.netgrif.application.engine.workflow.service.CaseSearchService; +import com.netgrif.application.engine.workflow.service.LegacyCaseSearchService; import com.netgrif.application.engine.workflow.service.interfaces.IWorkflowService; import com.netgrif.application.engine.workflow.web.responsebodies.MessageResource; import com.querydsl.core.types.Predicate; @@ -43,7 +43,7 @@ public class ElasticController { private IWorkflowService workflowService; @Autowired - private CaseSearchService searchService; + private LegacyCaseSearchService searchService; @Autowired private ReindexingTask reindexingTask; diff --git a/src/main/java/com/netgrif/application/engine/petrinet/service/PetriNetService.java b/src/main/java/com/netgrif/application/engine/petrinet/service/PetriNetService.java index 9009183cdce..e182f0d7481 100644 --- a/src/main/java/com/netgrif/application/engine/petrinet/service/PetriNetService.java +++ b/src/main/java/com/netgrif/application/engine/petrinet/service/PetriNetService.java @@ -38,6 +38,7 @@ import com.netgrif.application.engine.workflow.service.interfaces.IEventService; import com.netgrif.application.engine.workflow.service.interfaces.IFieldActionsCacheService; import com.netgrif.application.engine.workflow.service.interfaces.IWorkflowService; +import com.querydsl.core.types.Predicate; import lombok.extern.slf4j.Slf4j; import org.apache.tomcat.util.http.fileupload.IOUtils; import org.bson.Document; @@ -522,6 +523,36 @@ public Page search(PetriNetSearch criteriaClass, LoggedUser u return new PageImpl<>(nets.stream().map(net -> new PetriNetReference(net, locale)).collect(Collectors.toList()), pageable, mongoTemplate.count(queryTotal, PetriNet.class)); } + @Override + public Page search(Predicate predicate, Pageable pageable) { + if (pageable == null) { + pageable = Pageable.unpaged(); + } + if (predicate != null) { + // todo 2443 logged user permissions + return repository.findAll(predicate, pageable); + } + return Page.empty(); + } + + @Override + public long count(Predicate predicate) { + if (predicate != null) { + // todo 2443 logged user permissions + return repository.count(predicate); + } + return 0; + } + + @Override + public boolean exists(Predicate predicate) { + if (predicate != null) { + // todo 2443 logged user permissions + return repository.exists(predicate); + } + return false; + } + private void addValueCriteria(Query query, Query queryTotal, Criteria criteria) { query.addCriteria(criteria); queryTotal.addCriteria(criteria); diff --git a/src/main/java/com/netgrif/application/engine/petrinet/service/interfaces/IPetriNetService.java b/src/main/java/com/netgrif/application/engine/petrinet/service/interfaces/IPetriNetService.java index 85a9b7c0a7a..53b1a4a013f 100644 --- a/src/main/java/com/netgrif/application/engine/petrinet/service/interfaces/IPetriNetService.java +++ b/src/main/java/com/netgrif/application/engine/petrinet/service/interfaces/IPetriNetService.java @@ -15,6 +15,7 @@ import com.netgrif.application.engine.petrinet.web.responsebodies.PetriNetReference; import com.netgrif.application.engine.petrinet.web.responsebodies.TransitionReference; import com.netgrif.application.engine.workflow.domain.eventoutcomes.petrinetoutcomes.ImportPetriNetEventOutcome; +import com.querydsl.core.types.Predicate; import org.bson.types.ObjectId; import org.springframework.core.io.FileSystemResource; import org.springframework.data.domain.Page; @@ -91,6 +92,12 @@ static DataFieldReference transformToReference(PetriNet net, Transition transiti Page search(PetriNetSearch criteria, LoggedUser user, Pageable pageable, Locale locale); + Page search(Predicate predicate, Pageable pageable); + + long count(Predicate predicate); + + boolean exists(Predicate predicate); + Optional findByImportId(String id); void evictAllCaches(); diff --git a/src/main/java/com/netgrif/application/engine/pfql/service/IResourceSearchService.java b/src/main/java/com/netgrif/application/engine/pfql/service/IResourceSearchService.java new file mode 100644 index 00000000000..963a3043a7f --- /dev/null +++ b/src/main/java/com/netgrif/application/engine/pfql/service/IResourceSearchService.java @@ -0,0 +1,38 @@ +package com.netgrif.application.engine.pfql.service; + + +import com.netgrif.application.engine.pfql.domain.enums.QueryType; +import org.springframework.data.domain.Page; + +/** + * Service interface for searching resources using query language expressions. + *

+ * Provides a unified contract for executing search operations on specific resource types + * using both string-based queries and pre-evaluated query expressions. Implementations + * of this interface should handle the translation of query language syntax into the + * appropriate search mechanism (e.g., MongoDB queries, Elasticsearch queries). + *

+ *

+ * This interface supports various search operations including single result retrieval, + * paginated searches, counting matches, and existence checks. Each operation can be + * performed using either a raw query string or a pre-evaluated {@link QueryLangEvaluator}. + *

+ * + * @param the type of resource this service searches for (e.g., Case, Task, User) + */ +public interface IResourceSearchService { + + QueryType getQueryType(); + + Resource searchOne(String queryString); + Resource searchOne(QueryLangEvaluator evaluator); + + Page searchAll(String queryString); + Page searchAll(QueryLangEvaluator evaluator); + + long count(String queryString); + long count(QueryLangEvaluator evaluator); + + boolean exists(String queryString); + boolean exists(QueryLangEvaluator evaluator); +} diff --git a/src/main/java/com/netgrif/application/engine/pfql/service/ISearchService.java b/src/main/java/com/netgrif/application/engine/pfql/service/ISearchService.java index 6de405b1737..1ea796443f4 100644 --- a/src/main/java/com/netgrif/application/engine/pfql/service/ISearchService.java +++ b/src/main/java/com/netgrif/application/engine/pfql/service/ISearchService.java @@ -5,7 +5,7 @@ public interface ISearchService { Object search(String query); - Long count(String query); + long count(String query); boolean exists(String query); } diff --git a/src/main/java/com/netgrif/application/engine/pfql/service/QueryLangEvaluator.java b/src/main/java/com/netgrif/application/engine/pfql/service/QueryLangEvaluator.java index c920cd766bc..a1bcc40278f 100644 --- a/src/main/java/com/netgrif/application/engine/pfql/service/QueryLangEvaluator.java +++ b/src/main/java/com/netgrif/application/engine/pfql/service/QueryLangEvaluator.java @@ -35,6 +35,9 @@ public class QueryLangEvaluator extends QueryLangBaseListener { + // todo 2443 review + // todo 2443 split code into dedicated evaluators + private final ParseTreeProperty elasticQuery = new ParseTreeProperty<>(); private final ParseTreeProperty mongoQuery = new ParseTreeProperty<>(); diff --git a/src/main/java/com/netgrif/application/engine/pfql/service/SearchService.java b/src/main/java/com/netgrif/application/engine/pfql/service/SearchService.java index 9742d5f4453..a666a9c130f 100644 --- a/src/main/java/com/netgrif/application/engine/pfql/service/SearchService.java +++ b/src/main/java/com/netgrif/application/engine/pfql/service/SearchService.java @@ -1,182 +1,93 @@ package com.netgrif.application.engine.pfql.service; -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.pfql.domain.enums.QueryType; import com.netgrif.application.engine.pfql.service.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.netgrif.application.engine.workflow.service.interfaces.IWorkflowService; -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 java.util.Map; +import java.util.function.Function; +import java.util.stream.Collectors; import static com.netgrif.application.engine.pfql.service.utils.SearchUtils.evaluateQuery; @Slf4j @Service -@RequiredArgsConstructor public class SearchService implements ISearchService { - private final PetriNetRepository petriNetRepository; - private final IElasticCaseService elasticCaseService; - private final IWorkflowService workflowService; - private final CaseRepository caseRepository; - private final TaskRepository taskRepository; - private final UserRepository userRepository; - private final IUserService userService; + private final Map> serviceRegistry; - @Override - public String explainQuery(String input) { - return SearchUtils.explainQuery(input); - } - - public List searchCases(String query) { - QueryLangEvaluator evaluator = evaluateQuery(query); - Pageable pageable = evaluator.getPageable(); - - if (!evaluator.getSearchWithElastic()) { - Predicate predicate = evaluator.getFullMongoQuery(); - return caseRepository.findAll(predicate, pageable).getContent(); - } + public SearchService(List> services) { + this.serviceRegistry = services.stream() + .collect(Collectors.toMap(IResourceSearchService::getQueryType, Function.identity())); - String elasticQuery = evaluator.getFullElasticQuery(); - return findCasesElastic(elasticQuery, pageable); } - public Case searchCase(String query) { - QueryLangEvaluator evaluator = evaluateQuery(query); - Pageable pageable = evaluator.getPageable(); - - if (!evaluator.getSearchWithElastic()) { - Predicate predicate = evaluator.getFullMongoQuery(); - return caseRepository.findAll(predicate, PageRequest.of(0, 1)) - .getContent().stream().findFirst().orElse(null); - } - - String elasticQuery = evaluator.getFullElasticQuery(); - List cases = findCasesElastic(elasticQuery, pageable); - return cases.stream().findFirst().orElse(null); + /** + * Explains the provided query by parsing and describing its structure. + * + * @param input the query string to be explained + * @return a human-readable explanation of the query structure + */ + @Override + public String explainQuery(String input) { + log.debug("Explaining query: {}", input); + String explanation = SearchUtils.explainQuery(input); + log.trace("Query explanation result: {}", explanation); + return explanation; } + /** + * Executes a search operation based on the provided query string. + * Evaluates the query and delegates to the appropriate resource search service. + * Returns either a single result or multiple results based on the query specification. + * + * @param input the query string to be executed + * @return a single resource object or a page of resources depending on the query type + */ @Override public Object search(String input) { + log.debug("Executing search with query: {}", 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; + log.trace("Evaluated query type: {}, multiple: {}", evaluator.getType(), evaluator.getMultiple()); + IResourceSearchService service = this.serviceRegistry.get(evaluator.getType()); + Object result = evaluator.getMultiple() ? service.searchAll(evaluator) : service.searchOne(evaluator); + log.debug("Search completed, returning {} result", evaluator.getMultiple() ? "multiple" : "single"); + return result; } + /** + * Counts the number of resources that match the provided query string. + * + * @param input the query string to be evaluated + * @return the count of matching resources + */ @Override - public Long count(String input) { + public long count(String input) { + log.debug("Counting resources with query: {}", 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; + log.trace("Evaluated query type for count: {}", evaluator.getType()); + IResourceSearchService service = this.serviceRegistry.get(evaluator.getType()); + long count = service.count(evaluator); + log.debug("Count completed, result: {}", count); + return count; } + /** + * Checks whether any resources exist that match the provided query string. + * + * @param input the query string to be evaluated + * @return true if at least one matching resource exists, false otherwise + */ @Override public boolean exists(String input) { + log.debug("Checking existence with query: {}", 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; - } - - 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; + log.trace("Evaluated query type for exists: {}", evaluator.getType()); + IResourceSearchService service = this.serviceRegistry.get(evaluator.getType()); + boolean exists = service.exists(evaluator); + log.debug("Existence check completed, result: {}", exists); + return exists; } } diff --git a/src/main/java/com/netgrif/application/engine/pfql/service/caseresource/CaseSearchService.java b/src/main/java/com/netgrif/application/engine/pfql/service/caseresource/CaseSearchService.java new file mode 100644 index 00000000000..040d2da677f --- /dev/null +++ b/src/main/java/com/netgrif/application/engine/pfql/service/caseresource/CaseSearchService.java @@ -0,0 +1,236 @@ +package com.netgrif.application.engine.pfql.service.caseresource; + +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.pfql.domain.enums.QueryType; +import com.netgrif.application.engine.pfql.service.IResourceSearchService; +import com.netgrif.application.engine.pfql.service.QueryLangEvaluator; +import com.netgrif.application.engine.workflow.domain.Case; +import com.netgrif.application.engine.workflow.service.interfaces.IWorkflowService; +import lombok.RequiredArgsConstructor; +import lombok.extern.slf4j.Slf4j; +import org.springframework.context.i18n.LocaleContextHolder; +import org.springframework.data.domain.Page; +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.pfql.service.utils.SearchUtils.evaluateQuery; + +/** + * Service implementation for searching and querying Case resources. + * Supports both MongoDB and Elasticsearch-based searches depending on the query configuration. + * Provides methods to search for single or multiple cases, count matching cases, and check existence. + */ +@Slf4j +@Service +@RequiredArgsConstructor +public class CaseSearchService implements IResourceSearchService { + + private final IWorkflowService workflowService; + private final IElasticCaseService elasticCaseService; + private final IUserService userService; + + /** + * Returns the query type handled by this service. + * + * @return the QueryType.CASE indicating this service handles case queries + */ + @Override + public QueryType getQueryType() { + return QueryType.CASE; + } + + /** + * Searches for a single case matching the provided query string. + * The query string is evaluated and processed before execution. + * + * @param queryString the query string to be evaluated and executed + * @return the first matching Case, or null if no match is found + */ + @Override + public Case searchOne(String queryString) { + log.debug("Searching for single case with query: {}", queryString); + return searchOne(evaluateQuery(queryString)); + } + + /** + * Searches for a single case using a pre-evaluated query evaluator. + * Routes the search to either Elasticsearch or MongoDB based on the evaluator configuration. + * + * @param evaluator the query evaluator containing the parsed query and configuration + * @return the first matching Case, or null if no match is found + * @throws IllegalArgumentException if the evaluator is null or configured for multiple results + */ + @Override + public Case searchOne(QueryLangEvaluator evaluator) { + if (evaluator == null) { + throw new IllegalArgumentException("Query cannot be null"); + } + if (evaluator.getMultiple()) { + throw new IllegalArgumentException("Cannot use searchOne() with a query that expects multiple results. Use searchAll() instead."); + } + + log.debug("Searching for single case using {}", evaluator.getSearchWithElastic() ? "Elasticsearch" : "MongoDB"); + if (evaluator.getSearchWithElastic()) { + log.trace("Executing Elasticsearch query: {}", evaluator.getFullElasticQuery()); + Page caseInPage = findCasesElastic(evaluator.getFullElasticQuery(), PageRequest.of(0, 1)); + Case result = caseInPage.getContent().stream().findFirst().orElse(null); + log.trace("Elasticsearch search one result: {}", result != null ? result.getStringId() : "null"); + return result; + } else { + log.trace("Executing MongoDB query: {}", evaluator.getFullMongoQuery()); + Case result = workflowService.searchOne(evaluator.getFullMongoQuery()); + log.trace("MongoDB search one result: {}", result != null ? result.getStringId() : "null"); + return result; + } + } + + /** + * Searches for all cases matching the provided query string. + * The query string is evaluated and processed before execution. + * + * @param queryString the query string to be evaluated and executed + * @return a Page containing all matching Cases + */ + @Override + public Page searchAll(String queryString) { + log.debug("Searching for all cases with query: {}", queryString); + return searchAll(evaluateQuery(queryString)); + } + + /** + * Searches for all cases using a pre-evaluated query evaluator. + * Routes the search to either Elasticsearch or MongoDB based on the evaluator configuration. + * Supports pagination through the evaluator's pageable configuration. + * + * @param evaluator the query evaluator containing the parsed query, pagination, and configuration + * @return a Page containing all matching Cases + * @throws IllegalArgumentException if the evaluator is null or configured for single result + */ + @Override + public Page searchAll(QueryLangEvaluator evaluator) { + if (evaluator == null) { + throw new IllegalArgumentException("Query cannot be null"); + } + if (!evaluator.getMultiple()) { + throw new IllegalArgumentException("Cannot use searchAll() with a query that expects single result. Use searchOne() instead."); + } + + log.debug("Searching for all cases using {} with pagination: page={}, size={}", + evaluator.getSearchWithElastic() ? "Elasticsearch" : "MongoDB", + evaluator.getPageable().getPageNumber(), evaluator.getPageable().getPageSize()); + if (evaluator.getSearchWithElastic()) { + log.trace("Executing Elasticsearch query: {}", evaluator.getFullElasticQuery()); + Page result = findCasesElastic(evaluator.getFullElasticQuery(), evaluator.getPageable()); + log.trace("Elasticsearch search all result: page size={}, total elements={}", result.getNumberOfElements(), result.getTotalElements()); + return result; + } else { + log.trace("Executing MongoDB query: {}", evaluator.getFullMongoQuery()); + Page result = workflowService.search(evaluator.getFullMongoQuery(), evaluator.getPageable()); + log.trace("MongoDB search all result: page size={}, total elements={}", result.getNumberOfElements(), result.getTotalElements()); + return result; + } + } + + /** + * Counts the number of cases matching the provided query string. + * The query string is evaluated and processed before execution. + * + * @param queryString the query string to be evaluated and executed + * @return the count of matching cases + */ + @Override + public long count(String queryString) { + log.debug("Counting cases with query: {}", queryString); + return count(evaluateQuery(queryString)); + } + + /** + * Counts the number of cases using a pre-evaluated query evaluator. + * Routes the count operation to either Elasticsearch or MongoDB based on the evaluator configuration. + * + * @param evaluator the query evaluator containing the parsed query and configuration + * @return the count of matching cases + * @throws IllegalArgumentException if the evaluator is null + */ + @Override + public long count(QueryLangEvaluator evaluator) { + if (evaluator == null) { + throw new IllegalArgumentException("Query cannot be null"); + } + log.debug("Counting cases using {}", evaluator.getSearchWithElastic() ? "Elasticsearch" : "MongoDB"); + if (evaluator.getSearchWithElastic()) { + log.trace("Executing Elasticsearch count query: {}", evaluator.getFullElasticQuery()); + long result = countCasesElastic(evaluator.getFullElasticQuery()); + log.trace("Elasticsearch count result: {}", result); + return result; + } else { + log.trace("Executing MongoDB count query: {}", evaluator.getFullMongoQuery()); + long result = workflowService.count(evaluator.getFullMongoQuery()); + log.trace("MongoDB count result: {}", result); + return result; + } + } + + /** + * Checks whether any cases exist that match the provided query string. + * The query string is evaluated and processed before execution. + * + * @param queryString the query string to be evaluated and executed + * @return true if at least one matching case exists, false otherwise + */ + @Override + public boolean exists(String queryString) { + log.debug("Checking existence of case with query: {}", queryString); + return exists(evaluateQuery(queryString)); + } + + /** + * Checks whether any cases exist using a pre-evaluated query evaluator. + * Routes the existence check to either Elasticsearch or MongoDB based on the evaluator configuration. + * + * @param evaluator the query evaluator containing the parsed query and configuration + * @return true if at least one matching case exists, false otherwise + * @throws IllegalArgumentException if the evaluator is null + */ + @Override + public boolean exists(QueryLangEvaluator evaluator) { + if (evaluator == null) { + throw new IllegalArgumentException("Query cannot be null"); + } + log.debug("Checking existence of cases using {}", evaluator.getSearchWithElastic() ? "Elasticsearch" : "MongoDB"); + if (evaluator.getSearchWithElastic()) { + log.trace("Executing Elasticsearch exists query: {}", evaluator.getFullElasticQuery()); + boolean result = existsCasesElastic(evaluator.getFullElasticQuery()); + log.trace("Elasticsearch exists result: {}", result); + return result; + } else { + log.trace("Executing MongoDB exists query: {}", evaluator.getFullMongoQuery()); + boolean result = workflowService.exists(evaluator.getFullMongoQuery()); + log.trace("MongoDB exists result: {}", result); + return result; + } + } + + 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 Page 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); + } + + private boolean existsCasesElastic(String elasticQuery) { + return countCasesElastic(elasticQuery) > 0; + } +} diff --git a/src/main/java/com/netgrif/application/engine/pfql/service/processresource/ProcessSearchService.java b/src/main/java/com/netgrif/application/engine/pfql/service/processresource/ProcessSearchService.java new file mode 100644 index 00000000000..be8c2e07143 --- /dev/null +++ b/src/main/java/com/netgrif/application/engine/pfql/service/processresource/ProcessSearchService.java @@ -0,0 +1,216 @@ +package com.netgrif.application.engine.pfql.service.processresource; + +import com.netgrif.application.engine.petrinet.domain.PetriNet; +import com.netgrif.application.engine.petrinet.service.interfaces.IPetriNetService; +import com.netgrif.application.engine.pfql.domain.enums.QueryType; +import com.netgrif.application.engine.pfql.service.IResourceSearchService; +import com.netgrif.application.engine.pfql.service.QueryLangEvaluator; +import lombok.RequiredArgsConstructor; +import lombok.extern.slf4j.Slf4j; +import org.springframework.data.domain.Page; +import org.springframework.data.domain.PageRequest; +import org.springframework.stereotype.Service; + +import java.util.Optional; + +import static com.netgrif.application.engine.pfql.service.utils.SearchUtils.evaluateQuery; + +@Slf4j +@Service +@RequiredArgsConstructor +public class ProcessSearchService implements IResourceSearchService { + + private final IPetriNetService petriNetService; + + /** + * Returns the query type handled by this service. + * + * @return {@link QueryType#PROCESS} indicating this service handles process queries + */ + @Override + public QueryType getQueryType() { + return QueryType.PROCESS; + } + + /** + * Searches for a single process that matches the provided query string. + *

+ * This method parses the query string into an evaluator and delegates to + * {@link #searchOne(QueryLangEvaluator)} for execution. + *

+ * + * @param queryString the query string to be evaluated and executed + * @return the matching {@link PetriNet} process, or null if no match is found + * @throws IllegalArgumentException if the query string results in a multiple-results query + */ + @Override + public PetriNet searchOne(String queryString) { + log.debug("Searching for single process with query: {}", queryString); + return searchOne(evaluateQuery(queryString)); + } + + /** + * Searches for a single process using a pre-evaluated query expression. + *

+ * This method validates that the evaluator is configured for single-result queries + * and currently executes the search using MongoDB. Future implementations will + * support Elasticsearch as an alternative search backend. + *

+ * + * @param evaluator the query evaluator containing the parsed query and search configuration + * @return the matching {@link PetriNet} process, or null if no match is found + * @throws IllegalArgumentException if evaluator is null or configured for multiple results + */ + @Override + public PetriNet searchOne(QueryLangEvaluator evaluator) { + if (evaluator == null) { + throw new IllegalArgumentException("Query cannot be null"); + } + if (evaluator.getMultiple()) { + throw new IllegalArgumentException("Cannot use searchOne() with a query that expects multiple results. Use searchAll() instead."); + } + + // todo implement Elasticsearch search (service layer and evaluator layer) + + log.debug("Searching for single process using MongoDB"); + log.trace("Executing MongoDB query: {}", evaluator.getFullMongoQuery()); + Page processAsPage = petriNetService.search(evaluator.getFullMongoQuery(), PageRequest.of(0, 1)); + Optional processOpt = processAsPage.getContent().stream().findFirst(); + log.trace("MongoDB search one result: {}", processOpt.isPresent() ? processOpt.get().getStringId() : "null"); + return processOpt.orElse(null); + } + + /** + * Searches for all processes that match the provided query string. + *

+ * This method parses the query string into an evaluator and delegates to + * {@link #searchAll(QueryLangEvaluator)} for execution. + *

+ * + * @param queryString the query string to be evaluated and executed + * @return a page of matching {@link PetriNet} processes + * @throws IllegalArgumentException if the query string results in a single-result query + */ + @Override + public Page searchAll(String queryString) { + log.debug("Searching for all processes with query: {}", queryString); + return searchAll(evaluateQuery(queryString)); + } + + /** + * Searches for all processes using a pre-evaluated query expression. + *

+ * This method validates that the evaluator is configured for multiple-result queries + * and executes the search with pagination support. Currently uses MongoDB as the + * search backend. Future implementations will support Elasticsearch. + *

+ * + * @param evaluator the query evaluator containing the parsed query, pagination, and search configuration + * @return a page of matching {@link PetriNet} processes + * @throws IllegalArgumentException if evaluator is null or configured for single result + */ + @Override + public Page searchAll(QueryLangEvaluator evaluator) { + if (evaluator == null) { + throw new IllegalArgumentException("Query cannot be null"); + } + if (!evaluator.getMultiple()) { + throw new IllegalArgumentException("Cannot use searchAll() with a query that expects single result. Use searchOne() instead."); + } + + // todo implement Elasticsearch search (service layer and evaluator layer) + + log.debug("Searching for all processes using MongoDB"); + log.trace("Executing MongoDB query: {}", evaluator.getFullMongoQuery()); + Page result = petriNetService.search(evaluator.getFullMongoQuery(), evaluator.getPageable()); + log.trace("MongoDB search all result: page size={}, total elements={}", result.getNumberOfElements(), result.getTotalElements()); + return result; + } + + /** + * Counts the number of processes that match the provided query string. + *

+ * This method parses the query string into an evaluator and delegates to + * {@link #count(QueryLangEvaluator)} for execution. + *

+ * + * @param queryString the query string to be evaluated and executed + * @return the count of matching processes + */ + @Override + public long count(String queryString) { + log.debug("Counting processes with query: {}", queryString); + return count(evaluateQuery(queryString)); + } + + /** + * Counts the number of processes using a pre-evaluated query expression. + *

+ * This method executes a count operation without retrieving the actual process data. + * Currently uses MongoDB as the search backend. Future implementations will support + * Elasticsearch. + *

+ * + * @param evaluator the query evaluator containing the parsed query and search configuration + * @return the count of processes matching the query + * @throws IllegalArgumentException if evaluator is null + */ + @Override + public long count(QueryLangEvaluator evaluator) { + if (evaluator == null) { + throw new IllegalArgumentException("Query cannot be null"); + } + + // todo implement Elasticsearch search (service layer and evaluator layer) + + log.debug("Counting processes using MongoDB"); + log.trace("Executing MongoDB count query: {}", evaluator.getFullMongoQuery()); + long result = petriNetService.count(evaluator.getFullMongoQuery()); + log.trace("MongoDB count result: {}", result); + return result; + } + + /** + * Checks whether any processes exist that match the provided query string. + *

+ * This method parses the query string into an evaluator and delegates to + * {@link #exists(QueryLangEvaluator)} for execution. + *

+ * + * @param queryString the query string to be evaluated and executed + * @return true if at least one matching process exists, false otherwise + */ + @Override + public boolean exists(String queryString) { + log.debug("Checking existence of process with query: {}", queryString); + return exists(evaluateQuery(queryString)); + } + + /** + * Checks whether any processes exist using a pre-evaluated query expression. + *

+ * This method performs an existence check without retrieving or counting the actual + * process data, making it more efficient than count or search operations when only + * existence needs to be verified. Currently uses MongoDB as the search backend. + * Future implementations will support Elasticsearch. + *

+ * + * @param evaluator the query evaluator containing the parsed query and search configuration + * @return true if at least one matching process exists, false otherwise + * @throws IllegalArgumentException if evaluator is null + */ + @Override + public boolean exists(QueryLangEvaluator evaluator) { + if (evaluator == null) { + throw new IllegalArgumentException("Query cannot be null"); + } + + // todo implement Elasticsearch search (service layer and evaluator layer) + + log.debug("Checking existence of processes using MongoDB"); + log.trace("Executing MongoDB exists query: {}", evaluator.getFullMongoQuery()); + boolean result = petriNetService.exists(evaluator.getFullMongoQuery()); + log.trace("MongoDB exists result: {}", result); + return result; + } +} diff --git a/src/main/java/com/netgrif/application/engine/pfql/service/taskresource/TaskSearchService.java b/src/main/java/com/netgrif/application/engine/pfql/service/taskresource/TaskSearchService.java new file mode 100644 index 00000000000..4ec09d5ce99 --- /dev/null +++ b/src/main/java/com/netgrif/application/engine/pfql/service/taskresource/TaskSearchService.java @@ -0,0 +1,69 @@ +package com.netgrif.application.engine.pfql.service.taskresource; + +import com.netgrif.application.engine.pfql.domain.enums.QueryType; +import com.netgrif.application.engine.pfql.service.IResourceSearchService; +import com.netgrif.application.engine.pfql.service.QueryLangEvaluator; +import com.netgrif.application.engine.workflow.domain.Task; +import lombok.RequiredArgsConstructor; +import lombok.extern.slf4j.Slf4j; +import org.springframework.data.domain.Page; +import org.springframework.stereotype.Service; + +import static com.netgrif.application.engine.pfql.service.utils.SearchUtils.evaluateQuery; + +@Slf4j +@Service +@RequiredArgsConstructor +public class TaskSearchService implements IResourceSearchService { + // todo 2443 javadoc + // todo 2443 logging + + @Override + public QueryType getQueryType() { + return QueryType.TASK; + } + + @Override + public Task searchOne(String queryString) { + log.debug("Searching for single task with query: {}", queryString); + return searchOne(evaluateQuery(queryString)); + } + + @Override + public Task searchOne(QueryLangEvaluator evaluator) { + return null; + } + + @Override + public Page searchAll(String queryString) { + log.debug("Searching for all tasks with query: {}", queryString); + return searchAll(evaluateQuery(queryString)); + } + + @Override + public Page searchAll(QueryLangEvaluator evaluator) { + return null; + } + + @Override + public long count(String queryString) { + log.debug("Counting tasks with query: {}", queryString); + return count(evaluateQuery(queryString)); + } + + @Override + public long count(QueryLangEvaluator evaluator) { + return 0; + } + + @Override + public boolean exists(String queryString) { + log.debug("Checking existence of task with query: {}", queryString); + return exists(evaluateQuery(queryString)); + } + + @Override + public boolean exists(QueryLangEvaluator evaluator) { + return false; + } +} diff --git a/src/main/java/com/netgrif/application/engine/pfql/service/userresource/UserSearchService.java b/src/main/java/com/netgrif/application/engine/pfql/service/userresource/UserSearchService.java new file mode 100644 index 00000000000..1bd8aa20ee9 --- /dev/null +++ b/src/main/java/com/netgrif/application/engine/pfql/service/userresource/UserSearchService.java @@ -0,0 +1,63 @@ +package com.netgrif.application.engine.pfql.service.userresource; + +import com.netgrif.application.engine.auth.domain.IUser; +import com.netgrif.application.engine.pfql.domain.enums.QueryType; +import com.netgrif.application.engine.pfql.service.IResourceSearchService; +import com.netgrif.application.engine.pfql.service.QueryLangEvaluator; +import lombok.RequiredArgsConstructor; +import lombok.extern.slf4j.Slf4j; +import org.springframework.data.domain.Page; +import org.springframework.stereotype.Service; + +@Slf4j +@Service +@RequiredArgsConstructor +public class UserSearchService implements IResourceSearchService { + // todo 2443 javadoc + // todo 2443 logging + + @Override + public QueryType getQueryType() { + return QueryType.USER; + } + + @Override + public IUser searchOne(String queryString) { + return null; + } + + @Override + public IUser searchOne(QueryLangEvaluator evaluator) { + return null; + } + + @Override + public Page searchAll(String queryString) { + return null; + } + + @Override + public Page searchAll(QueryLangEvaluator evaluator) { + return null; + } + + @Override + public long count(String queryString) { + return 0; + } + + @Override + public long count(QueryLangEvaluator evaluator) { + return 0; + } + + @Override + public boolean exists(String queryString) { + return false; + } + + @Override + public boolean exists(QueryLangEvaluator evaluator) { + return false; + } +} diff --git a/src/main/java/com/netgrif/application/engine/workflow/service/CaseSearchService.java b/src/main/java/com/netgrif/application/engine/workflow/service/LegacyCaseSearchService.java similarity index 98% rename from src/main/java/com/netgrif/application/engine/workflow/service/CaseSearchService.java rename to src/main/java/com/netgrif/application/engine/workflow/service/LegacyCaseSearchService.java index 1a2fcc0316f..9d07ca2821f 100644 --- a/src/main/java/com/netgrif/application/engine/workflow/service/CaseSearchService.java +++ b/src/main/java/com/netgrif/application/engine/workflow/service/LegacyCaseSearchService.java @@ -31,9 +31,11 @@ import java.util.stream.Collectors; @Service -public class CaseSearchService extends MongoSearchService { +public class LegacyCaseSearchService extends MongoSearchService { - private static final Logger log = LoggerFactory.getLogger(CaseSearchService.class.getName()); + // todo 2443 remove? + + private static final Logger log = LoggerFactory.getLogger(LegacyCaseSearchService.class.getName()); public static final String ROLE = "role"; public static final String DATA = "data"; diff --git a/src/main/java/com/netgrif/application/engine/workflow/service/TaskSearchService.java b/src/main/java/com/netgrif/application/engine/workflow/service/LegacyTaskSearchService.java similarity index 99% rename from src/main/java/com/netgrif/application/engine/workflow/service/TaskSearchService.java rename to src/main/java/com/netgrif/application/engine/workflow/service/LegacyTaskSearchService.java index aace6fb0348..e4dec1184ca 100644 --- a/src/main/java/com/netgrif/application/engine/workflow/service/TaskSearchService.java +++ b/src/main/java/com/netgrif/application/engine/workflow/service/LegacyTaskSearchService.java @@ -19,7 +19,7 @@ import java.util.stream.Collectors; @Service -public class TaskSearchService extends MongoSearchService { +public class LegacyTaskSearchService extends MongoSearchService { @Autowired private IPetriNetService petriNetService; diff --git a/src/main/java/com/netgrif/application/engine/workflow/service/TaskService.java b/src/main/java/com/netgrif/application/engine/workflow/service/TaskService.java index 910e7590002..08d8cd84210 100644 --- a/src/main/java/com/netgrif/application/engine/workflow/service/TaskService.java +++ b/src/main/java/com/netgrif/application/engine/workflow/service/TaskService.java @@ -82,7 +82,7 @@ public class TaskService implements ITaskService { protected MongoTemplate mongoTemplate; @Autowired - protected TaskSearchService searchService; + protected LegacyTaskSearchService searchService; @Autowired @Qualifier("taskScheduler") diff --git a/src/main/java/com/netgrif/application/engine/workflow/service/WorkflowService.java b/src/main/java/com/netgrif/application/engine/workflow/service/WorkflowService.java index 87bcb62a025..a38de6a39e7 100644 --- a/src/main/java/com/netgrif/application/engine/workflow/service/WorkflowService.java +++ b/src/main/java/com/netgrif/application/engine/workflow/service/WorkflowService.java @@ -5,7 +5,6 @@ import com.netgrif.application.engine.auth.service.interfaces.IUserService; import com.netgrif.application.engine.elastic.service.interfaces.IElasticCaseMappingService; import com.netgrif.application.engine.elastic.service.interfaces.IElasticCaseService; -import com.netgrif.application.engine.elastic.web.requestbodies.CaseSearchRequest; import com.netgrif.application.engine.history.domain.caseevents.CreateCaseEventLog; import com.netgrif.application.engine.history.domain.caseevents.DeleteCaseEventLog; import com.netgrif.application.engine.history.service.IHistoryService; @@ -75,7 +74,7 @@ public class WorkflowService implements IWorkflowService { protected ITaskService taskService; @Autowired - protected CaseSearchService searchService; + protected LegacyCaseSearchService searchService; @Autowired protected ApplicationEventPublisher publisher; @@ -188,6 +187,7 @@ public Page findAllByUri(String uri, Pageable pageable) { public Page search(Predicate predicate, Pageable pageable) { Page page = repository.findAll(predicate, pageable); page.getContent().forEach(this::setPetriNet); + decryptDataSets(page.getContent()); return setImmediateDataFields(page); } @@ -215,6 +215,26 @@ public long count(Map request, LoggedUser user, Locale locale) { } } + @Override + public long count(Predicate predicate) { + // todo 2443 logged user permissions + if (predicate != null) { + return repository.count(predicate); + } else { + return 0; + } + } + + @Override + public boolean exists(Predicate predicate) { + // todo 2443 logged user permissions + if (predicate != null) { + return repository.exists(predicate); + } else { + return false; + } + } + @Override public Case resolveUserRef(Case useCase) { useCase.getUsers().clear(); diff --git a/src/main/java/com/netgrif/application/engine/workflow/service/interfaces/IWorkflowService.java b/src/main/java/com/netgrif/application/engine/workflow/service/interfaces/IWorkflowService.java index 7eb8318173a..f9e347b56fd 100644 --- a/src/main/java/com/netgrif/application/engine/workflow/service/interfaces/IWorkflowService.java +++ b/src/main/java/com/netgrif/application/engine/workflow/service/interfaces/IWorkflowService.java @@ -75,6 +75,10 @@ public interface IWorkflowService { long count(Map request, LoggedUser user, Locale locale); + long count(Predicate predicate); + + boolean exists(Predicate predicate); + // List getCaseFieldChoices(Pageable pageable, String caseId, String fieldId); boolean removeTasksFromCase(List tasks, String caseId); diff --git a/src/test/groovy/com/netgrif/application/engine/workflow/CaseSearchTest.groovy b/src/test/groovy/com/netgrif/application/engine/workflow/CaseSearchTest.groovy index 4c78adbe4a6..33407446074 100644 --- a/src/test/groovy/com/netgrif/application/engine/workflow/CaseSearchTest.groovy +++ b/src/test/groovy/com/netgrif/application/engine/workflow/CaseSearchTest.groovy @@ -9,7 +9,7 @@ import com.netgrif.application.engine.petrinet.service.interfaces.IPetriNetServi import com.netgrif.application.engine.startup.ImportHelper import com.netgrif.application.engine.startup.SuperCreator import com.netgrif.application.engine.workflow.domain.Case -import com.netgrif.application.engine.workflow.service.CaseSearchService +import com.netgrif.application.engine.workflow.service.LegacyCaseSearchService import org.junit.jupiter.api.BeforeEach import org.junit.jupiter.api.Disabled import org.junit.jupiter.api.Test @@ -179,10 +179,10 @@ class CaseSearchTest { String buildRequestBody(String fullText) { def map = [ - (CaseSearchService.PETRINET): [ - (CaseSearchService.PETRINET_IDENTIFIER): "case_search_test.xml" + (LegacyCaseSearchService.PETRINET): [ + (LegacyCaseSearchService.PETRINET_IDENTIFIER): "case_search_test.xml" ], - (CaseSearchService.FULLTEXT): fullText + (LegacyCaseSearchService.FULLTEXT): fullText ] ObjectMapper mapper = new ObjectMapper() diff --git a/src/test/groovy/com/netgrif/application/engine/workflow/TaskControllerTest.groovy b/src/test/groovy/com/netgrif/application/engine/workflow/TaskControllerTest.groovy index e30f8af7f85..89f55920109 100644 --- a/src/test/groovy/com/netgrif/application/engine/workflow/TaskControllerTest.groovy +++ b/src/test/groovy/com/netgrif/application/engine/workflow/TaskControllerTest.groovy @@ -21,7 +21,7 @@ import com.netgrif.application.engine.utils.FullPageRequest import com.netgrif.application.engine.workflow.domain.Case import com.netgrif.application.engine.workflow.domain.DataField import com.netgrif.application.engine.workflow.domain.Task -import com.netgrif.application.engine.workflow.service.TaskSearchService +import com.netgrif.application.engine.workflow.service.LegacyTaskSearchService import com.netgrif.application.engine.workflow.service.TaskService import com.netgrif.application.engine.workflow.service.interfaces.IDataService import com.netgrif.application.engine.workflow.service.interfaces.IWorkflowService @@ -50,7 +50,7 @@ class TaskControllerTest { private TaskService taskService @Autowired - private TaskSearchService taskSearchService + private LegacyTaskSearchService taskSearchService @Autowired private IElasticTaskService elasticTaskService From 02ad04ab7653a1e3e69a29c0a69253bf8e5e2604 Mon Sep 17 00:00:00 2001 From: chvostek Date: Thu, 4 Jun 2026 14:18:27 +0200 Subject: [PATCH 05/17] [NAE-2443] PFQL support - finish code encapsulation of SearchService --- .../caseresource/CaseSearchService.java | 2 +- .../taskresource/TaskSearchService.java | 210 +++++++++++++++++- .../engine/workflow/service/TaskService.java | 18 +- .../service/interfaces/ITaskService.java | 4 + .../engine/pfql/QueryLangTest.java | 5 +- 5 files changed, 229 insertions(+), 10 deletions(-) diff --git a/src/main/java/com/netgrif/application/engine/pfql/service/caseresource/CaseSearchService.java b/src/main/java/com/netgrif/application/engine/pfql/service/caseresource/CaseSearchService.java index 040d2da677f..218a07557f4 100644 --- a/src/main/java/com/netgrif/application/engine/pfql/service/caseresource/CaseSearchService.java +++ b/src/main/java/com/netgrif/application/engine/pfql/service/caseresource/CaseSearchService.java @@ -216,7 +216,7 @@ public boolean exists(QueryLangEvaluator evaluator) { } } - private Long countCasesElastic(String elasticQuery) { + private long countCasesElastic(String elasticQuery) { CaseSearchRequest caseSearchRequest = new CaseSearchRequest(); caseSearchRequest.query = elasticQuery; return elasticCaseService.count(List.of(caseSearchRequest), userService.getLoggedOrSystem().transformToLoggedUser(), diff --git a/src/main/java/com/netgrif/application/engine/pfql/service/taskresource/TaskSearchService.java b/src/main/java/com/netgrif/application/engine/pfql/service/taskresource/TaskSearchService.java index 4ec09d5ce99..e692fa6ea15 100644 --- a/src/main/java/com/netgrif/application/engine/pfql/service/taskresource/TaskSearchService.java +++ b/src/main/java/com/netgrif/application/engine/pfql/service/taskresource/TaskSearchService.java @@ -1,69 +1,267 @@ package com.netgrif.application.engine.pfql.service.taskresource; +import com.netgrif.application.engine.auth.service.interfaces.IUserService; +import com.netgrif.application.engine.elastic.service.interfaces.IElasticTaskService; +import com.netgrif.application.engine.elastic.web.requestbodies.ElasticTaskSearchRequest; import com.netgrif.application.engine.pfql.domain.enums.QueryType; import com.netgrif.application.engine.pfql.service.IResourceSearchService; import com.netgrif.application.engine.pfql.service.QueryLangEvaluator; import com.netgrif.application.engine.workflow.domain.Task; +import com.netgrif.application.engine.workflow.service.interfaces.ITaskService; import lombok.RequiredArgsConstructor; import lombok.extern.slf4j.Slf4j; +import org.springframework.context.i18n.LocaleContextHolder; import org.springframework.data.domain.Page; +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.pfql.service.utils.SearchUtils.evaluateQuery; +/** + * Service implementation for searching Task resources using query language expressions. + *

+ * Provides functionality to search for tasks using both MongoDB and Elasticsearch backends. + * The service automatically determines which search backend to use based on the query + * evaluator configuration. It supports single result retrieval, paginated searches, + * counting matches, and existence checks. + *

+ */ @Slf4j @Service @RequiredArgsConstructor public class TaskSearchService implements IResourceSearchService { - // todo 2443 javadoc - // todo 2443 logging + private final ITaskService taskService; + private final IElasticTaskService elasticTaskService; + private final IUserService userService; + + /** + * Returns the query type handled by this service. + * + * @return {@link QueryType#TASK} indicating this service handles task queries + */ @Override public QueryType getQueryType() { return QueryType.TASK; } + /** + * Searches for a single task matching the provided query string. + * + * @param queryString the query string to be evaluated and executed + * @return the matching task, or null if no task is found + */ @Override public Task searchOne(String queryString) { log.debug("Searching for single task with query: {}", queryString); return searchOne(evaluateQuery(queryString)); } + /** + * Searches for a single task using a pre-evaluated query expression. + *

+ * The search is executed using either Elasticsearch or MongoDB based on the + * evaluator configuration. This method validates that the query expects a single + * result and throws an exception if multiple results are expected. + *

+ * + * @param evaluator the pre-evaluated query expression containing search criteria + * @return the matching task, or null if no task is found + * @throws IllegalArgumentException if evaluator is null or if the query expects multiple results + */ @Override public Task searchOne(QueryLangEvaluator evaluator) { - return null; + if (evaluator == null) { + throw new IllegalArgumentException("Query cannot be null"); + } + if (evaluator.getMultiple()) { + throw new IllegalArgumentException("Cannot use searchOne() with a query that expects multiple results. Use searchAll() instead."); + } + + log.debug("Searching for single task using {}", evaluator.getSearchWithElastic() ? "Elasticsearch" : "MongoDB"); + if (evaluator.getSearchWithElastic()) { + log.trace("Executing Elasticsearch query: {}", evaluator.getFullElasticQuery()); + Page taskInPage = findTasksElastic(evaluator.getFullElasticQuery(), PageRequest.of(0, 1)); + Task result = taskInPage.getContent().stream().findFirst().orElse(null); + log.trace("Elasticsearch search one result: {}", result != null ? result.getStringId() : "null"); + return result; + } else { + log.trace("Executing MongoDB query: {}", evaluator.getFullMongoQuery()); + Task result = taskService.searchOne(evaluator.getFullMongoQuery()); + log.trace("MongoDB search one result: {}", result != null ? result.getStringId() : "null"); + return result; + } } + /** + * Searches for all tasks matching the provided query string. + * + * @param queryString the query string to be evaluated and executed + * @return a page of matching tasks + */ @Override public Page searchAll(String queryString) { log.debug("Searching for all tasks with query: {}", queryString); return searchAll(evaluateQuery(queryString)); } + /** + * Searches for all tasks using a pre-evaluated query expression. + *

+ * The search is executed using either Elasticsearch or MongoDB based on the + * evaluator configuration. This method validates that the query expects multiple + * results and throws an exception if a single result is expected. + *

+ * + * @param evaluator the pre-evaluated query expression containing search criteria and pagination info + * @return a page of matching tasks + * @throws IllegalArgumentException if evaluator is null or if the query expects a single result + */ @Override public Page searchAll(QueryLangEvaluator evaluator) { - return null; + if (evaluator == null) { + throw new IllegalArgumentException("Query cannot be null"); + } + if (!evaluator.getMultiple()) { + throw new IllegalArgumentException("Cannot use searchAll() with a query that expects single result. Use searchOne() instead."); + } + + log.debug("Searching for all tasks using {}", evaluator.getSearchWithElastic() ? "Elasticsearch" : "MongoDB"); + if (evaluator.getSearchWithElastic()) { + log.trace("Executing Elasticsearch query: {}", evaluator.getFullElasticQuery()); + Page result = findTasksElastic(evaluator.getFullElasticQuery(), evaluator.getPageable()); + log.trace("Elasticsearch search all result: page size={}, total elements={}", result.getNumberOfElements(), result.getTotalElements()); + return result; + } else { + log.trace("Executing MongoDB query: {}", evaluator.getFullMongoQuery()); + Page result = taskService.search(evaluator.getFullMongoQuery(), evaluator.getPageable()); + log.trace("MongoDB search all result: page size={}, total elements={}", result.getNumberOfElements(), result.getTotalElements()); + return result; + } } + /** + * Counts the number of tasks matching the provided query string. + * + * @param queryString the query string to be evaluated and executed + * @return the count of matching tasks + */ @Override public long count(String queryString) { log.debug("Counting tasks with query: {}", queryString); return count(evaluateQuery(queryString)); } + /** + * Counts the number of tasks using a pre-evaluated query expression. + *

+ * The count is executed using either Elasticsearch or MongoDB based on the + * evaluator configuration. + *

+ * + * @param evaluator the pre-evaluated query expression containing search criteria + * @return the count of matching tasks + * @throws IllegalArgumentException if evaluator is null + */ @Override public long count(QueryLangEvaluator evaluator) { - return 0; + if (evaluator == null) { + throw new IllegalArgumentException("Query cannot be null"); + } + log.debug("Counting tasks using {}", evaluator.getSearchWithElastic() ? "Elasticsearch" : "MongoDB"); + if (evaluator.getSearchWithElastic()) { + log.trace("Executing Elasticsearch count query: {}", evaluator.getFullElasticQuery()); + long result = countTasksElastic(evaluator.getFullElasticQuery()); + log.trace("Elasticsearch count result: {}", result); + return result; + } else { + log.trace("Executing MongoDB count query: {}", evaluator.getFullMongoQuery()); + long result = taskService.count(evaluator.getFullMongoQuery()); + log.trace("MongoDB count result: {}", result); + return result; + } } + /** + * Checks whether any tasks exist that match the provided query string. + * + * @param queryString the query string to be evaluated and executed + * @return true if at least one matching task exists, false otherwise + */ @Override public boolean exists(String queryString) { log.debug("Checking existence of task with query: {}", queryString); return exists(evaluateQuery(queryString)); } + /** + * Checks whether any tasks exist using a pre-evaluated query expression. + *

+ * The existence check is executed using either Elasticsearch or MongoDB based on the + * evaluator configuration. + *

+ * + * @param evaluator the pre-evaluated query expression containing search criteria + * @return true if at least one matching task exists, false otherwise + * @throws IllegalArgumentException if evaluator is null + */ @Override public boolean exists(QueryLangEvaluator evaluator) { - return false; + if (evaluator == null) { + throw new IllegalArgumentException("Query cannot be null"); + } + log.debug("Checking existence of tasks using {}", evaluator.getSearchWithElastic() ? "Elasticsearch" : "MongoDB"); + if (evaluator.getSearchWithElastic()) { + log.trace("Executing Elasticsearch exists query: {}", evaluator.getFullElasticQuery()); + boolean result = existsTasksElastic(evaluator.getFullElasticQuery()); + log.trace("Elasticsearch exists result: {}", result); + return result; + } else { + log.trace("Executing MongoDB exists query: {}", evaluator.getFullMongoQuery()); + boolean result = taskService.exists(evaluator.getFullMongoQuery()); + log.trace("MongoDB exists result: {}", result); + return result; + } + } + + /** + * Counts tasks using Elasticsearch. + * + * @param elasticQuery the Elasticsearch query string + * @return the count of matching tasks + */ + private long countTasksElastic(String elasticQuery) { + ElasticTaskSearchRequest taskSearchRequest = new ElasticTaskSearchRequest(); + taskSearchRequest.query = elasticQuery; + return elasticTaskService.count(List.of(taskSearchRequest), userService.getLoggedOrSystem().transformToLoggedUser(), + LocaleContextHolder.getLocale(), false); + } + + /** + * Finds tasks using Elasticsearch with pagination support. + * + * @param elasticQuery the Elasticsearch query string + * @param pageable the pagination information + * @return a page of matching tasks + */ + private Page findTasksElastic(String elasticQuery, Pageable pageable) { + ElasticTaskSearchRequest taskSearchRequest = new ElasticTaskSearchRequest(); + taskSearchRequest.query = elasticQuery; + return elasticTaskService.search(List.of(taskSearchRequest), userService.getLoggedOrSystem().transformToLoggedUser(), + pageable, LocaleContextHolder.getLocale(), false); + } + + /** + * Checks whether any tasks exist using Elasticsearch. + * + * @param elasticQuery the Elasticsearch query string + * @return true if at least one matching task exists, false otherwise + */ + private boolean existsTasksElastic(String elasticQuery) { + return countTasksElastic(elasticQuery) > 0; } + } diff --git a/src/main/java/com/netgrif/application/engine/workflow/service/TaskService.java b/src/main/java/com/netgrif/application/engine/workflow/service/TaskService.java index 08d8cd84210..6747c0700a3 100644 --- a/src/main/java/com/netgrif/application/engine/workflow/service/TaskService.java +++ b/src/main/java/com/netgrif/application/engine/workflow/service/TaskService.java @@ -690,6 +690,18 @@ public long count(List requests, LoggedUser user, Locale loca } } + @Override + public long count(com.querydsl.core.types.Predicate predicate) { + // todo 2443 logged user permissions + return predicate != null ? taskRepository.count(predicate) : 0; + } + + @Override + public boolean exists(com.querydsl.core.types.Predicate predicate) { + // todo 2443 logged user permissions + return predicate != null && taskRepository.exists(predicate); + } + @Override public Page findByCases(Pageable pageable, List cases) { return loadUsers(taskRepository.findByCaseIdIn(pageable, cases)); @@ -733,15 +745,19 @@ public Page searchAll(com.querydsl.core.types.Predicate predicate) { @Override public Page search(com.querydsl.core.types.Predicate predicate, Pageable pageable) { + // todo 2443 logged user permissions Page tasks = taskRepository.findAll(predicate, pageable); return loadUsers(tasks); } @Override public Task searchOne(com.querydsl.core.types.Predicate predicate) { + // todo 2443 logged user permissions Page tasks = taskRepository.findAll(predicate, PageRequest.of(0, 1)); - if (tasks.getTotalElements() > 0) + if (tasks.getTotalElements() > 0) { + loadUsers(tasks); return tasks.getContent().get(0); + } return null; } diff --git a/src/main/java/com/netgrif/application/engine/workflow/service/interfaces/ITaskService.java b/src/main/java/com/netgrif/application/engine/workflow/service/interfaces/ITaskService.java index 691b2de4a9b..0a5e825b973 100644 --- a/src/main/java/com/netgrif/application/engine/workflow/service/interfaces/ITaskService.java +++ b/src/main/java/com/netgrif/application/engine/workflow/service/interfaces/ITaskService.java @@ -33,6 +33,10 @@ public interface ITaskService { long count(List requests, LoggedUser user, Locale locale, Boolean isIntersection); + long count(com.querydsl.core.types.Predicate predicate); + + boolean exists(com.querydsl.core.types.Predicate predicate); + Page findByCases(Pageable pageable, List cases); List findAllById(List ids); diff --git a/src/test/groovy/com/netgrif/application/engine/pfql/QueryLangTest.java b/src/test/groovy/com/netgrif/application/engine/pfql/QueryLangTest.java index e575d2f4c59..9fadd1bf867 100644 --- a/src/test/groovy/com/netgrif/application/engine/pfql/QueryLangTest.java +++ b/src/test/groovy/com/netgrif/application/engine/pfql/QueryLangTest.java @@ -25,6 +25,7 @@ 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.Page; import org.springframework.data.domain.Pageable; import org.springframework.data.domain.Sort; import org.springframework.data.mongodb.core.MongoOperations; @@ -87,11 +88,11 @@ public void testSearchService() { log.info("Case with boolean_0 == true: {}", ((Case)case_5).getTitle()); cases = searchService.search("cases: processIdentifier eq 'query_test' and data.boolean_0.value == true"); - assert ((List)cases).size() == 5; + assert ((Page)cases).getTotalElements() == 5; // TODO: release/8.0.0 add 'neq'/'!=', simplify 'not' requires () cases = searchService.search("cases: processIdentifier eq 'query_test' and data.boolean_0.value == true and not (data.text_0.value == '4')"); - assert ((List)cases).size() == 4; + assert ((Page)cases).getTotalElements() == 4; } @Test From 0c62b293db73388401fe80ff75b2a1ca658e5943 Mon Sep 17 00:00:00 2001 From: chvostek Date: Thu, 4 Jun 2026 15:35:01 +0200 Subject: [PATCH 06/17] [NAE-2443] PFQL support - implement not equals operator --- .../engine/pfql/domain/antlr4/QueryLang.g4 | 15 +-- .../pfql/service/utils/SearchUtils.java | 30 ++++-- .../engine/pfql/QueryLangTest.java | 97 ++++++++++++++++++- 3 files changed, 126 insertions(+), 16 deletions(-) diff --git a/src/main/java/com/netgrif/application/engine/pfql/domain/antlr4/QueryLang.g4 b/src/main/java/com/netgrif/application/engine/pfql/domain/antlr4/QueryLang.g4 index 88c455c6c03..f51bd010e13 100644 --- a/src/main/java/com/netgrif/application/engine/pfql/domain/antlr4/QueryLang.g4 +++ b/src/main/java/com/netgrif/application/engine/pfql/domain/antlr4/QueryLang.g4 @@ -1,4 +1,4 @@ -// todo NAE-1997: generate this with plugin +// todo 2443 generate this with plugin grammar QueryLang; query: resource=(PROCESS | PROCESSES) delimeter processConditions (paging)? (processSorting)? EOF # processQuery @@ -223,12 +223,12 @@ tasksUserIdComparison: tasksUserId SPACE stringComparison # tasksUserIdBasic ; // 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 ; +objectIdComparison: (NOT SPACE?)? op=(EQ | NEQ) SPACE STRING ; +stringComparison: (NOT SPACE?)? op=(EQ | NEQ | CONTAINS | LT | GT | LTE | GTE) SPACE STRING ; +numberComparison: (NOT SPACE?)? op=(EQ | NEQ | LT | GT | LTE | GTE) SPACE number=(INT | DOUBLE) ; +dateComparison: (NOT SPACE?)? op=(EQ | NEQ | LT | GT | LTE | GTE) SPACE DATE ; +dateTimeComparison: (NOT SPACE?)? op=(EQ | NEQ | LT | GT | LTE | GTE) SPACE DATETIME ; +booleanComparison: (NOT SPACE?)? op=(EQ | NEQ) SPACE BOOLEAN ; // in list/in range comparisons inListStringComparison: (NOT SPACE?)? op=IN SPACE stringList ; @@ -252,6 +252,7 @@ AND: A N D | '&' ; OR: O R | '|' ; NOT: N O T | '!' ; EQ: E Q | '==' ; +NEQ: N E Q | '!=' ; LT: L T | '<' ; GT: G T | '>' ; LTE: L T E | '<=' ; diff --git a/src/main/java/com/netgrif/application/engine/pfql/service/utils/SearchUtils.java b/src/main/java/com/netgrif/application/engine/pfql/service/utils/SearchUtils.java index 7a36dda75b0..2bb468d1e5b 100644 --- a/src/main/java/com/netgrif/application/engine/pfql/service/utils/SearchUtils.java +++ b/src/main/java/com/netgrif/application/engine/pfql/service/utils/SearchUtils.java @@ -35,12 +35,12 @@ 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) + ComparisonType.ID, List.of(QueryLangParser.EQ, QueryLangParser.NEQ, QueryLangParser.IN), + ComparisonType.STRING, List.of(QueryLangParser.EQ, QueryLangParser.NEQ, QueryLangParser.CONTAINS, QueryLangParser.LT, QueryLangParser.LTE, QueryLangParser.GT, QueryLangParser.GTE), + ComparisonType.NUMBER, List.of(QueryLangParser.EQ, QueryLangParser.NEQ, QueryLangParser.LT, QueryLangParser.LTE, QueryLangParser.GT, QueryLangParser.GTE), + ComparisonType.DATE, List.of(QueryLangParser.EQ, QueryLangParser.NEQ, QueryLangParser.LT, QueryLangParser.LTE, QueryLangParser.GT, QueryLangParser.GTE), + ComparisonType.DATETIME, List.of(QueryLangParser.EQ, QueryLangParser.NEQ, QueryLangParser.LT, QueryLangParser.LTE, QueryLangParser.GT, QueryLangParser.GTE), + ComparisonType.BOOLEAN, List.of(QueryLangParser.EQ, QueryLangParser.NEQ) ); public static final Map processAttrToSortPropMapping = Map.of( @@ -196,9 +196,12 @@ public static void checkOp(ComparisonType type, Token op) { } public static Predicate buildObjectIdPredicate(QObjectId qObjectId, int op, ObjectId objectId, boolean not) { - if (op != QueryLangParser.EQ) { + if (op != QueryLangParser.EQ && op != QueryLangParser.NEQ) { throw new UnsupportedOperationException("Operator is not available for id comparison"); } + if (op == QueryLangParser.NEQ) { + not = !not; + } Predicate predicate = qObjectId.eq(objectId); if (not) { @@ -219,6 +222,9 @@ public static Predicate buildStringPredicate(StringPath stringPath, int op, Stri case QueryLangParser.EQ: predicate = stringPath.eq(string); break; + case QueryLangParser.NEQ: + predicate = stringPath.ne(string); + break; case QueryLangParser.CONTAINS: predicate = stringPath.contains(string); break; @@ -273,6 +279,9 @@ public static Predicate buildVersionPredicate(int op, String versionString, bool case QueryLangParser.EQ: predicate = qVersion.eq(new Version(major, minor, patch)); break; + case QueryLangParser.NEQ: + predicate = qVersion.ne(new Version(major, minor, patch)); + break; case QueryLangParser.GT: predicate = qVersion.major.gt(major) .or(qVersion.major.eq(major).and(qVersion.minor.gt(minor))) @@ -336,6 +345,9 @@ public static Predicate buildDateTimePredicate(DateTimePath dateT case QueryLangParser.EQ: predicate = dateTimePath.eq(localDateTime); break; + case QueryLangParser.NEQ: + predicate = dateTimePath.ne(localDateTime); + break; case QueryLangParser.LT: predicate = dateTimePath.lt(localDateTime); break; @@ -382,6 +394,10 @@ public static String buildElasticQuery(String attribute, int op, String value, b case QueryLangParser.IN: query = attribute + ":" + value; break; + case QueryLangParser.NEQ: + query = attribute + ":" + value; + not = !not; + break; case QueryLangParser.LT: query = attribute + ":<" + value; break; diff --git a/src/test/groovy/com/netgrif/application/engine/pfql/QueryLangTest.java b/src/test/groovy/com/netgrif/application/engine/pfql/QueryLangTest.java index 9fadd1bf867..3679030282a 100644 --- a/src/test/groovy/com/netgrif/application/engine/pfql/QueryLangTest.java +++ b/src/test/groovy/com/netgrif/application/engine/pfql/QueryLangTest.java @@ -90,8 +90,7 @@ public void testSearchService() { cases = searchService.search("cases: processIdentifier eq 'query_test' and data.boolean_0.value == true"); assert ((Page)cases).getTotalElements() == 5; - // TODO: release/8.0.0 add 'neq'/'!=', simplify 'not' requires () - cases = searchService.search("cases: processIdentifier eq 'query_test' and data.boolean_0.value == true and not (data.text_0.value == '4')"); + cases = searchService.search("cases: processIdentifier eq 'query_test' and data.boolean_0.value == true and data.text_0.value != '4'"); assert ((Page)cases).getTotalElements() == 4; } @@ -213,6 +212,11 @@ public void testComplexMongodbProcessQuery() { compareMongoQueries(mongoDbUtils, actual, expected); + actual = evaluateQuery(String.format("process: id neq '%s'", GENERIC_OBJECT_ID)).getFullMongoQuery(); + 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")); @@ -225,6 +229,11 @@ public void testComplexMongodbProcessQuery() { compareMongoQueries(mongoDbUtils, actual, expected); + actual = evaluateQuery(String.format("process: id eq '%s' and title != '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")); @@ -380,6 +389,11 @@ public void testComplexMongodbCaseQuery() { compareMongoQueries(mongoDbUtils, actual, expected); + actual = evaluateQuery(String.format("case: id neq '%s'", GENERIC_OBJECT_ID)).getFullMongoQuery(); + 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")); @@ -392,6 +406,11 @@ public void testComplexMongodbCaseQuery() { compareMongoQueries(mongoDbUtils, actual, expected); + actual = evaluateQuery(String.format("case: id eq '%s' and title != '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")); @@ -404,6 +423,11 @@ public void testComplexMongodbCaseQuery() { compareMongoQueries(mongoDbUtils, actual, expected); + actual = evaluateQuery(String.format("case: id eq '%s' or title neq '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"))); @@ -518,6 +542,11 @@ public void testComplexMongodbTaskQuery() { compareMongoQueries(mongoDbUtils, actual, expected); + actual = evaluateQuery(String.format("task: id != '%s'", GENERIC_OBJECT_ID)).getFullMongoQuery(); + 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")); @@ -530,6 +559,11 @@ public void testComplexMongodbTaskQuery() { compareMongoQueries(mongoDbUtils, actual, expected); + actual = evaluateQuery(String.format("task: id eq '%s' and title neq '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")); @@ -542,6 +576,11 @@ public void testComplexMongodbTaskQuery() { compareMongoQueries(mongoDbUtils, actual, expected); + actual = evaluateQuery(String.format("task: id eq '%s' or title neq '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"))); @@ -598,6 +637,11 @@ public void testComplexMongodbUserQuery() { compareMongoQueries(mongoDbUtils, actual, expected); + actual = evaluateQuery(String.format("user: id != '%s'", GENERIC_OBJECT_ID)).getFullMongoQuery(); + 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")); @@ -610,6 +654,11 @@ public void testComplexMongodbUserQuery() { compareMongoQueries(mongoDbUtils, actual, expected); + actual = evaluateQuery(String.format("user: id eq '%s' and email neq '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")); @@ -622,6 +671,11 @@ public void testComplexMongodbUserQuery() { compareMongoQueries(mongoDbUtils, actual, expected); + actual = evaluateQuery(String.format("user: id eq '%s' or email != '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"))); @@ -713,6 +767,9 @@ public void testComplexElasticProcessQuery() { String actual = evaluateQuery(String.format("process: id not eq '%s'", GENERIC_OBJECT_ID)).getFullElasticQuery(); assert actual == null; + actual = evaluateQuery(String.format("process: id neq '%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; @@ -721,6 +778,9 @@ public void testComplexElasticProcessQuery() { actual = evaluateQuery(String.format("process: id eq '%s' and title not eq 'test'", GENERIC_OBJECT_ID)).getFullElasticQuery(); assert actual == null; + actual = evaluateQuery(String.format("process: id eq '%s' and title != '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; @@ -729,6 +789,9 @@ public void testComplexElasticProcessQuery() { actual = evaluateQuery(String.format("process: id eq '%s' or title not eq 'test'", GENERIC_OBJECT_ID)).getFullElasticQuery(); assert actual == null; + actual = evaluateQuery(String.format("process: id eq '%s' or title != '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; @@ -836,6 +899,10 @@ public void testComplexElasticCaseQuery() { String expected = String.format("NOT stringId:%s", GENERIC_OBJECT_ID); assert expected.equals(actual); + actual = evaluateQuery(String.format("case: id neq '%s'", GENERIC_OBJECT_ID)).getFullElasticQuery(); + 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); @@ -846,6 +913,10 @@ public void testComplexElasticCaseQuery() { expected = String.format("stringId:%s AND NOT title:test", GENERIC_OBJECT_ID); assert expected.equals(actual); + actual = evaluateQuery(String.format("case: id eq '%s' and title != '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); @@ -856,6 +927,10 @@ public void testComplexElasticCaseQuery() { expected = String.format("stringId:%s OR NOT title:test", GENERIC_OBJECT_ID); assert expected.equals(actual); + actual = evaluateQuery(String.format("case: id eq '%s' or title neq '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); @@ -956,6 +1031,9 @@ public void testComplexElasticTaskQuery() { String actual = evaluateQuery(String.format("task: id not eq '%s'", GENERIC_OBJECT_ID)).getFullElasticQuery(); assert actual == null; + actual = evaluateQuery(String.format("task: id neq '%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; @@ -964,6 +1042,9 @@ public void testComplexElasticTaskQuery() { actual = evaluateQuery(String.format("task: id eq '%s' and title not eq 'test'", GENERIC_OBJECT_ID)).getFullElasticQuery(); assert actual == null; + actual = evaluateQuery(String.format("task: id eq '%s' and title != '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; @@ -972,6 +1053,9 @@ public void testComplexElasticTaskQuery() { actual = evaluateQuery(String.format("task: id eq '%s' or title not eq 'test'", GENERIC_OBJECT_ID)).getFullElasticQuery(); assert actual == null; + actual = evaluateQuery(String.format("task: id eq '%s' or title neq '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; @@ -1021,6 +1105,9 @@ public void testComplexElasticUserQuery() { String actual = evaluateQuery(String.format("user: id not eq '%s'", GENERIC_OBJECT_ID)).getFullElasticQuery(); assert actual == null; + actual = evaluateQuery(String.format("user: id != '%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; @@ -1029,6 +1116,9 @@ public void testComplexElasticUserQuery() { actual = evaluateQuery(String.format("user: id eq '%s' and email not eq 'test'", GENERIC_OBJECT_ID)).getFullElasticQuery(); assert actual == null; + actual = evaluateQuery(String.format("user: id eq '%s' and email neq '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; @@ -1037,6 +1127,9 @@ public void testComplexElasticUserQuery() { actual = evaluateQuery(String.format("user: id eq '%s' or email not eq 'test'", GENERIC_OBJECT_ID)).getFullElasticQuery(); assert actual == null; + actual = evaluateQuery(String.format("user: id eq '%s' or email != '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; From 19b4e04b9feeb225138bf4f36f26922459faae73 Mon Sep 17 00:00:00 2001 From: chvostek Date: Fri, 5 Jun 2026 09:41:52 +0200 Subject: [PATCH 07/17] [NAE-2443] PFQL support - allow no trailing white space after operator in --- .../engine/pfql/domain/antlr4/QueryLang.g4 | 16 ++++++++-------- .../application/engine/pfql/QueryLangTest.java | 10 +++++----- 2 files changed, 13 insertions(+), 13 deletions(-) diff --git a/src/main/java/com/netgrif/application/engine/pfql/domain/antlr4/QueryLang.g4 b/src/main/java/com/netgrif/application/engine/pfql/domain/antlr4/QueryLang.g4 index f51bd010e13..cfb134ff2ee 100644 --- a/src/main/java/com/netgrif/application/engine/pfql/domain/antlr4/QueryLang.g4 +++ b/src/main/java/com/netgrif/application/engine/pfql/domain/antlr4/QueryLang.g4 @@ -231,14 +231,14 @@ dateTimeComparison: (NOT SPACE?)? op=(EQ | NEQ | LT | GT | LTE | GTE) SPACE DATE booleanComparison: (NOT SPACE?)? op=(EQ | NEQ) 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 ; +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 ; diff --git a/src/test/groovy/com/netgrif/application/engine/pfql/QueryLangTest.java b/src/test/groovy/com/netgrif/application/engine/pfql/QueryLangTest.java index 3679030282a..a3777c97382 100644 --- a/src/test/groovy/com/netgrif/application/engine/pfql/QueryLangTest.java +++ b/src/test/groovy/com/netgrif/application/engine/pfql/QueryLangTest.java @@ -104,7 +104,7 @@ public void testSimpleMongodbProcessQuery() { compareMongoQueries(mongoDbUtils, actual, expected); - actual = evaluateQuery(String.format("process: id in ('%s', '%s')", GENERIC_OBJECT_ID, GENERIC_OBJECT_ID)).getFullMongoQuery(); + 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); @@ -149,17 +149,17 @@ public void testSimpleMongodbProcessQuery() { 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(); + 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(); + 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(); + 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))) @@ -171,7 +171,7 @@ public void testSimpleMongodbProcessQuery() { compareMongoQueries(mongoDbUtils, actual, expected); - actual = evaluateQuery("process: version in [1.1.1 : 2.2.2]").getFullMongoQuery(); + 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))) From 05eb5b8ef7d7d9bc376a918c31afb086b4b7aace Mon Sep 17 00:00:00 2001 From: chvostek Date: Fri, 5 Jun 2026 10:25:12 +0200 Subject: [PATCH 08/17] [NAE-2443] PFQL support - introduce action delegate PFQL methods - update validation in search services --- .../logic/action/ActionDelegate.groovy | 94 +++++++++++++++++++ .../pfql/service/IResourceSearchService.java | 24 ++++- .../pfql/service/QueryLangEvaluator.java | 46 ++++----- .../engine/pfql/service/SearchService.java | 14 +-- .../caseresource/CaseSearchService.java | 32 +++---- .../processresource/ProcessSearchService.java | 30 +++--- .../taskresource/TaskSearchService.java | 32 +++---- .../userresource/UserSearchService.java | 2 +- 8 files changed, 185 insertions(+), 89 deletions(-) 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 26791594b3e..e7f1cde0c26 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 @@ -48,6 +48,10 @@ 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.IProcessRoleService import com.netgrif.application.engine.petrinet.service.interfaces.IUriService +import com.netgrif.application.engine.pfql.service.ISearchService +import com.netgrif.application.engine.pfql.service.caseresource.CaseSearchService +import com.netgrif.application.engine.pfql.service.processresource.ProcessSearchService +import com.netgrif.application.engine.pfql.service.taskresource.TaskSearchService import com.netgrif.application.engine.rules.domain.RuleRepository import com.netgrif.application.engine.startup.DefaultFiltersRunner import com.netgrif.application.engine.startup.FilterRunner @@ -207,6 +211,18 @@ class ActionDelegate { @Autowired DashboardItemService dashboardItemService + @Autowired + ISearchService searchService + + @Autowired + CaseSearchService caseSearchService + + @Autowired + TaskSearchService taskSearchService + + @Autowired + ProcessSearchService processSearchService + FrontendActionOutcome Frontend /** @@ -2673,4 +2689,82 @@ class ActionDelegate { Case taskCase = workflowService.findOne(task.caseId) return taskCase.getPetriNet().getDataSet().get(fieldId) } + + // todo 2443 javadoc with examples for search methods + + Case searchCase(String query) { + return caseSearchService.searchOne(query) + } + + Page pagedSearchCases(String query) { + return caseSearchService.searchAll(query) + } + + List searchCases(String query) { + return pagedSearchCases(query).content + } + + long countCases(String query) { + return caseSearchService.count(query) + } + + boolean existsCase(String query) { + return caseSearchService.exists(query) + } + + Task searchTask(String query) { + return taskSearchService.searchOne(query) + } + + Page pagedSearchTasks(String query) { + return taskSearchService.searchAll(query) + } + + List searchTasks(String query) { + return pagedSearchTasks(query).content + } + + long countTasks(String query) { + return taskSearchService.count(query) + } + + boolean existsTask(String query) { + return taskSearchService.exists(query) + } + + PetriNet searchProcess(String query) { + return processSearchService.searchOne(query) + } + + Page pagedSearchProcesses(String query) { + return processSearchService.searchAll(query) + } + + List searchProcesses(String query) { + return pagedSearchProcesses(query).content + } + + long countProcesses(String query) { + return processSearchService.count(query) + } + + boolean existsProcess(String query) { + return processSearchService.exists(query) + } + + Object search(String query) { + Object result = searchService.search(query) + if (result instanceof Page) { + return result.content + } + return result + } + + long count(String query) { + return searchService.count(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/pfql/service/IResourceSearchService.java b/src/main/java/com/netgrif/application/engine/pfql/service/IResourceSearchService.java index 963a3043a7f..2271645c93c 100644 --- a/src/main/java/com/netgrif/application/engine/pfql/service/IResourceSearchService.java +++ b/src/main/java/com/netgrif/application/engine/pfql/service/IResourceSearchService.java @@ -22,7 +22,7 @@ */ public interface IResourceSearchService { - QueryType getQueryType(); + QueryType getQueryResourceType(); Resource searchOne(String queryString); Resource searchOne(QueryLangEvaluator evaluator); @@ -35,4 +35,26 @@ public interface IResourceSearchService { boolean exists(String queryString); boolean exists(QueryLangEvaluator evaluator); + + // todo 2443 javadoc + default void checkEvaluatorNotNull(QueryLangEvaluator evaluator) { + if (evaluator == null) { + throw new IllegalArgumentException("Query cannot be null"); + } + } + + // todo 2443 javadoc + default void checkEvaluatorMultiplicity(QueryLangEvaluator evaluator) { + if (evaluator.getMultiple()) { + throw new IllegalArgumentException("Cannot use searchOne() with a query that expects multiple results. Use searchAll() instead."); + } + } + + // todo 2443 javadoc + default void checkEvaluatorResourceType(QueryLangEvaluator evaluator) { + if (evaluator.getResourceType() != getQueryResourceType()) { + throw new IllegalArgumentException(String.format("Wrong query resource type. Should be: %s, was: %s", + getQueryResourceType(), evaluator.getResourceType())); + } + } } diff --git a/src/main/java/com/netgrif/application/engine/pfql/service/QueryLangEvaluator.java b/src/main/java/com/netgrif/application/engine/pfql/service/QueryLangEvaluator.java index a1bcc40278f..f661b9a35a4 100644 --- a/src/main/java/com/netgrif/application/engine/pfql/service/QueryLangEvaluator.java +++ b/src/main/java/com/netgrif/application/engine/pfql/service/QueryLangEvaluator.java @@ -42,7 +42,7 @@ public class QueryLangEvaluator extends QueryLangBaseListener { private final ParseTreeProperty mongoQuery = new ParseTreeProperty<>(); @Getter - private QueryType type; + private QueryType resourceType; @Getter private Boolean multiple; @Getter @@ -140,7 +140,7 @@ private void processConditionGroup(ParseTree child, ParseTree current, Boolean n @Override public void enterProcessQuery(QueryLangParser.ProcessQueryContext ctx) { - type = QueryType.PROCESS; + resourceType = QueryType.PROCESS; multiple = ctx.resource.getType() == QueryLangParser.PROCESSES; } @@ -154,7 +154,7 @@ public void exitProcessQuery(QueryLangParser.ProcessQueryContext ctx) { @Override public void enterCaseQuery(QueryLangParser.CaseQueryContext ctx) { - type = QueryType.CASE; + resourceType = QueryType.CASE; multiple = ctx.resource.getType() == QueryLangParser.CASES; } @@ -168,7 +168,7 @@ public void exitCaseQuery(QueryLangParser.CaseQueryContext ctx) { @Override public void enterTaskQuery(QueryLangParser.TaskQueryContext ctx) { - type = QueryType.TASK; + resourceType = QueryType.TASK; multiple = ctx.resource.getType() == QueryLangParser.TASKS; } @@ -182,7 +182,7 @@ public void exitTaskQuery(QueryLangParser.TaskQueryContext ctx) { @Override public void enterUserQuery(QueryLangParser.UserQueryContext ctx) { - type = QueryType.USER; + resourceType = QueryType.USER; multiple = ctx.resource.getType() == QueryLangParser.USERS; } @@ -374,7 +374,7 @@ public void exitIdBasic(QueryLangParser.IdBasicContext ctx) { checkOp(ComparisonType.ID, op); ObjectId objectId = getObjectIdValue(ctx.objectIdComparison().STRING().getText()); - switch (type) { + switch (resourceType) { case PROCESS: qObjectId = QPetriNet.petriNet._id; break; @@ -389,7 +389,7 @@ public void exitIdBasic(QueryLangParser.IdBasicContext ctx) { qObjectId = QUser.user._id; break; default: - throw new IllegalArgumentException("Unknown query type: " + type); + throw new IllegalArgumentException("Unknown query type: " + resourceType); } setMongoQuery(ctx, buildObjectIdPredicate(qObjectId, op.getType(), objectId, not)); @@ -408,7 +408,7 @@ public void exitIdList(QueryLangParser.IdListContext ctx) { .map(node -> getStringValue(node.getText())) .collect(Collectors.toList()); - switch (type) { + switch (resourceType) { case PROCESS: qObjectId = QPetriNet.petriNet._id; break; @@ -423,7 +423,7 @@ public void exitIdList(QueryLangParser.IdListContext ctx) { qObjectId = QUser.user._id; break; default: - throw new IllegalArgumentException("Unknown query type: " + type); + throw new IllegalArgumentException("Unknown query type: " + resourceType); } setMongoQuery(ctx, buildObjectIdPredicateInList(qObjectId, objectIdList, not)); @@ -436,7 +436,7 @@ public void exitTitleBasic(QueryLangParser.TitleBasicContext ctx) { boolean not = ctx.stringComparison().NOT() != null; String string = getStringValue(ctx.stringComparison().STRING().getText()); - switch (type) { + switch (resourceType) { case PROCESS: stringPath = QPetriNet.petriNet.title.defaultValue; break; @@ -448,7 +448,7 @@ public void exitTitleBasic(QueryLangParser.TitleBasicContext ctx) { stringPath = QTask.task.title.defaultValue; break; default: - throw new IllegalArgumentException("Unknown query type: " + type); + throw new IllegalArgumentException("Unknown query type: " + resourceType); } setMongoQuery(ctx, buildStringPredicate(stringPath, op.getType(), string, not)); @@ -460,7 +460,7 @@ public void exitTitleList(QueryLangParser.TitleListContext ctx) { boolean not = ctx.inListStringComparison().NOT() != null; List stringList = ctx.inListStringComparison().stringList().STRING().stream().map(node -> getStringValue(node.getText())).collect(Collectors.toList()); - switch (type) { + switch (resourceType) { case PROCESS: stringPath = QPetriNet.petriNet.title.defaultValue; break; @@ -472,7 +472,7 @@ public void exitTitleList(QueryLangParser.TitleListContext ctx) { stringPath = QTask.task.title.defaultValue; break; default: - throw new IllegalArgumentException("Unknown query type: " + type); + throw new IllegalArgumentException("Unknown query type: " + resourceType); } setMongoQuery(ctx, buildStringPredicateInList(stringPath, stringList, not)); @@ -487,7 +487,7 @@ public void exitTitleRange(QueryLangParser.TitleRangeContext ctx) { String leftString = getStringValue(ctx.inRangeStringComparison().stringRange().STRING(0).getText()); String rightString = getStringValue(ctx.inRangeStringComparison().stringRange().STRING(1).getText()); - switch (type) { + switch (resourceType) { case PROCESS: stringPath = QPetriNet.petriNet.title.defaultValue; break; @@ -499,7 +499,7 @@ public void exitTitleRange(QueryLangParser.TitleRangeContext ctx) { stringPath = QTask.task.title.defaultValue; break; default: - throw new IllegalArgumentException("Unknown query type: " + type); + throw new IllegalArgumentException("Unknown query type: " + resourceType); } setMongoQuery(ctx, buildStringPredicateInRange(stringPath, leftString, leftEndpointOpen, rightString, rightEndpointOpen, not)); @@ -571,7 +571,7 @@ public void exitCdDateBasic(QueryLangParser.CdDateBasicContext ctx) { boolean not = ctx.dateComparison().NOT() != null; LocalDateTime localDateTime = toDateTime(ctx.dateComparison().DATE().getText()); - switch (type) { + switch (resourceType) { case PROCESS: dateTimePath = QPetriNet.petriNet.creationDate; break; @@ -580,7 +580,7 @@ public void exitCdDateBasic(QueryLangParser.CdDateBasicContext ctx) { setElasticQuery(ctx, buildElasticQuery("creationDateSortable", op.getType(), String.valueOf(Timestamp.valueOf(localDateTime).getTime()), not)); break; default: - throw new IllegalArgumentException("Unknown query type: " + type); + throw new IllegalArgumentException("Unknown query type: " + resourceType); } setMongoQuery(ctx, buildDateTimePredicate(dateTimePath, op.getType(), localDateTime, not)); @@ -593,7 +593,7 @@ public void exitCdDateTimeBasic(QueryLangParser.CdDateTimeBasicContext ctx) { boolean not = ctx.dateTimeComparison().NOT() != null; LocalDateTime localDateTime = toDateTime(ctx.dateTimeComparison().DATETIME().getText()); - switch (type) { + switch (resourceType) { case PROCESS: dateTimePath = QPetriNet.petriNet.creationDate; break; @@ -602,7 +602,7 @@ public void exitCdDateTimeBasic(QueryLangParser.CdDateTimeBasicContext ctx) { setElasticQuery(ctx, buildElasticQuery("creationDateSortable", op.getType(), String.valueOf(Timestamp.valueOf(localDateTime).getTime()), not)); break; default: - throw new IllegalArgumentException("Unknown query type: " + type); + throw new IllegalArgumentException("Unknown query type: " + resourceType); } setMongoQuery(ctx, buildDateTimePredicate(dateTimePath, op.getType(), localDateTime, not)); @@ -615,7 +615,7 @@ public void exitCdDateList(QueryLangParser.CdDateListContext ctx) { 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) { + switch (resourceType) { case PROCESS: dateTimePath = QPetriNet.petriNet.creationDate; break; @@ -628,7 +628,7 @@ public void exitCdDateList(QueryLangParser.CdDateListContext ctx) { setElasticQuery(ctx, buildElasticQueryInList("creationDateSortable", timestampStringList, not)); break; default: - throw new IllegalArgumentException("Unknown query type: " + type); + throw new IllegalArgumentException("Unknown query type: " + resourceType); } setMongoQuery(ctx, buildDateTimePredicateInList(dateTimePath, stringDateList, not)); @@ -655,7 +655,7 @@ public void exitCdDateRange(QueryLangParser.CdDateRangeContext ctx) { } - switch (type) { + switch (resourceType) { case PROCESS: dateTimePath = QPetriNet.petriNet.creationDate; break; @@ -664,7 +664,7 @@ public void exitCdDateRange(QueryLangParser.CdDateRangeContext ctx) { 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); + throw new IllegalArgumentException("Unknown query type: " + resourceType); } setMongoQuery(ctx, buildDateTimePredicateInRange(dateTimePath, leftDateTime, leftEndpointOpen, rightDateTime, rightEndpointOpen, not)); diff --git a/src/main/java/com/netgrif/application/engine/pfql/service/SearchService.java b/src/main/java/com/netgrif/application/engine/pfql/service/SearchService.java index a666a9c130f..ec824677864 100644 --- a/src/main/java/com/netgrif/application/engine/pfql/service/SearchService.java +++ b/src/main/java/com/netgrif/application/engine/pfql/service/SearchService.java @@ -20,7 +20,7 @@ public class SearchService implements ISearchService { public SearchService(List> services) { this.serviceRegistry = services.stream() - .collect(Collectors.toMap(IResourceSearchService::getQueryType, Function.identity())); + .collect(Collectors.toMap(IResourceSearchService::getQueryResourceType, Function.identity())); } @@ -50,8 +50,8 @@ public String explainQuery(String input) { public Object search(String input) { log.debug("Executing search with query: {}", input); QueryLangEvaluator evaluator = evaluateQuery(input); - log.trace("Evaluated query type: {}, multiple: {}", evaluator.getType(), evaluator.getMultiple()); - IResourceSearchService service = this.serviceRegistry.get(evaluator.getType()); + log.trace("Evaluated query type: {}, multiple: {}", evaluator.getResourceType(), evaluator.getMultiple()); + IResourceSearchService service = this.serviceRegistry.get(evaluator.getResourceType()); Object result = evaluator.getMultiple() ? service.searchAll(evaluator) : service.searchOne(evaluator); log.debug("Search completed, returning {} result", evaluator.getMultiple() ? "multiple" : "single"); return result; @@ -67,8 +67,8 @@ public Object search(String input) { public long count(String input) { log.debug("Counting resources with query: {}", input); QueryLangEvaluator evaluator = evaluateQuery(input); - log.trace("Evaluated query type for count: {}", evaluator.getType()); - IResourceSearchService service = this.serviceRegistry.get(evaluator.getType()); + log.trace("Evaluated query type for count: {}", evaluator.getResourceType()); + IResourceSearchService service = this.serviceRegistry.get(evaluator.getResourceType()); long count = service.count(evaluator); log.debug("Count completed, result: {}", count); return count; @@ -84,8 +84,8 @@ public long count(String input) { public boolean exists(String input) { log.debug("Checking existence with query: {}", input); QueryLangEvaluator evaluator = evaluateQuery(input); - log.trace("Evaluated query type for exists: {}", evaluator.getType()); - IResourceSearchService service = this.serviceRegistry.get(evaluator.getType()); + log.trace("Evaluated query type for exists: {}", evaluator.getResourceType()); + IResourceSearchService service = this.serviceRegistry.get(evaluator.getResourceType()); boolean exists = service.exists(evaluator); log.debug("Existence check completed, result: {}", exists); return exists; diff --git a/src/main/java/com/netgrif/application/engine/pfql/service/caseresource/CaseSearchService.java b/src/main/java/com/netgrif/application/engine/pfql/service/caseresource/CaseSearchService.java index 218a07557f4..710c0914394 100644 --- a/src/main/java/com/netgrif/application/engine/pfql/service/caseresource/CaseSearchService.java +++ b/src/main/java/com/netgrif/application/engine/pfql/service/caseresource/CaseSearchService.java @@ -40,7 +40,7 @@ public class CaseSearchService implements IResourceSearchService { * @return the QueryType.CASE indicating this service handles case queries */ @Override - public QueryType getQueryType() { + public QueryType getQueryResourceType() { return QueryType.CASE; } @@ -67,12 +67,9 @@ public Case searchOne(String queryString) { */ @Override public Case searchOne(QueryLangEvaluator evaluator) { - if (evaluator == null) { - throw new IllegalArgumentException("Query cannot be null"); - } - if (evaluator.getMultiple()) { - throw new IllegalArgumentException("Cannot use searchOne() with a query that expects multiple results. Use searchAll() instead."); - } + checkEvaluatorNotNull(evaluator); + checkEvaluatorMultiplicity(evaluator); + checkEvaluatorResourceType(evaluator); log.debug("Searching for single case using {}", evaluator.getSearchWithElastic() ? "Elasticsearch" : "MongoDB"); if (evaluator.getSearchWithElastic()) { @@ -113,12 +110,9 @@ public Page searchAll(String queryString) { */ @Override public Page searchAll(QueryLangEvaluator evaluator) { - if (evaluator == null) { - throw new IllegalArgumentException("Query cannot be null"); - } - if (!evaluator.getMultiple()) { - throw new IllegalArgumentException("Cannot use searchAll() with a query that expects single result. Use searchOne() instead."); - } + checkEvaluatorNotNull(evaluator); + checkEvaluatorMultiplicity(evaluator); + checkEvaluatorResourceType(evaluator); log.debug("Searching for all cases using {} with pagination: page={}, size={}", evaluator.getSearchWithElastic() ? "Elasticsearch" : "MongoDB", @@ -159,9 +153,9 @@ public long count(String queryString) { */ @Override public long count(QueryLangEvaluator evaluator) { - if (evaluator == null) { - throw new IllegalArgumentException("Query cannot be null"); - } + checkEvaluatorNotNull(evaluator); + checkEvaluatorResourceType(evaluator); + log.debug("Counting cases using {}", evaluator.getSearchWithElastic() ? "Elasticsearch" : "MongoDB"); if (evaluator.getSearchWithElastic()) { log.trace("Executing Elasticsearch count query: {}", evaluator.getFullElasticQuery()); @@ -199,9 +193,9 @@ public boolean exists(String queryString) { */ @Override public boolean exists(QueryLangEvaluator evaluator) { - if (evaluator == null) { - throw new IllegalArgumentException("Query cannot be null"); - } + checkEvaluatorNotNull(evaluator); + checkEvaluatorResourceType(evaluator); + log.debug("Checking existence of cases using {}", evaluator.getSearchWithElastic() ? "Elasticsearch" : "MongoDB"); if (evaluator.getSearchWithElastic()) { log.trace("Executing Elasticsearch exists query: {}", evaluator.getFullElasticQuery()); diff --git a/src/main/java/com/netgrif/application/engine/pfql/service/processresource/ProcessSearchService.java b/src/main/java/com/netgrif/application/engine/pfql/service/processresource/ProcessSearchService.java index be8c2e07143..af218bb683f 100644 --- a/src/main/java/com/netgrif/application/engine/pfql/service/processresource/ProcessSearchService.java +++ b/src/main/java/com/netgrif/application/engine/pfql/service/processresource/ProcessSearchService.java @@ -28,7 +28,7 @@ public class ProcessSearchService implements IResourceSearchService { * @return {@link QueryType#PROCESS} indicating this service handles process queries */ @Override - public QueryType getQueryType() { + public QueryType getQueryResourceType() { return QueryType.PROCESS; } @@ -63,12 +63,9 @@ public PetriNet searchOne(String queryString) { */ @Override public PetriNet searchOne(QueryLangEvaluator evaluator) { - if (evaluator == null) { - throw new IllegalArgumentException("Query cannot be null"); - } - if (evaluator.getMultiple()) { - throw new IllegalArgumentException("Cannot use searchOne() with a query that expects multiple results. Use searchAll() instead."); - } + checkEvaluatorNotNull(evaluator); + checkEvaluatorMultiplicity(evaluator); + checkEvaluatorResourceType(evaluator); // todo implement Elasticsearch search (service layer and evaluator layer) @@ -111,12 +108,9 @@ public Page searchAll(String queryString) { */ @Override public Page searchAll(QueryLangEvaluator evaluator) { - if (evaluator == null) { - throw new IllegalArgumentException("Query cannot be null"); - } - if (!evaluator.getMultiple()) { - throw new IllegalArgumentException("Cannot use searchAll() with a query that expects single result. Use searchOne() instead."); - } + checkEvaluatorNotNull(evaluator); + checkEvaluatorMultiplicity(evaluator); + checkEvaluatorResourceType(evaluator); // todo implement Elasticsearch search (service layer and evaluator layer) @@ -157,9 +151,8 @@ public long count(String queryString) { */ @Override public long count(QueryLangEvaluator evaluator) { - if (evaluator == null) { - throw new IllegalArgumentException("Query cannot be null"); - } + checkEvaluatorNotNull(evaluator); + checkEvaluatorResourceType(evaluator); // todo implement Elasticsearch search (service layer and evaluator layer) @@ -201,9 +194,8 @@ public boolean exists(String queryString) { */ @Override public boolean exists(QueryLangEvaluator evaluator) { - if (evaluator == null) { - throw new IllegalArgumentException("Query cannot be null"); - } + checkEvaluatorNotNull(evaluator); + checkEvaluatorResourceType(evaluator); // todo implement Elasticsearch search (service layer and evaluator layer) diff --git a/src/main/java/com/netgrif/application/engine/pfql/service/taskresource/TaskSearchService.java b/src/main/java/com/netgrif/application/engine/pfql/service/taskresource/TaskSearchService.java index e692fa6ea15..2a49a3af218 100644 --- a/src/main/java/com/netgrif/application/engine/pfql/service/taskresource/TaskSearchService.java +++ b/src/main/java/com/netgrif/application/engine/pfql/service/taskresource/TaskSearchService.java @@ -44,7 +44,7 @@ public class TaskSearchService implements IResourceSearchService { * @return {@link QueryType#TASK} indicating this service handles task queries */ @Override - public QueryType getQueryType() { + public QueryType getQueryResourceType() { return QueryType.TASK; } @@ -74,12 +74,9 @@ public Task searchOne(String queryString) { */ @Override public Task searchOne(QueryLangEvaluator evaluator) { - if (evaluator == null) { - throw new IllegalArgumentException("Query cannot be null"); - } - if (evaluator.getMultiple()) { - throw new IllegalArgumentException("Cannot use searchOne() with a query that expects multiple results. Use searchAll() instead."); - } + checkEvaluatorNotNull(evaluator); + checkEvaluatorMultiplicity(evaluator); + checkEvaluatorResourceType(evaluator); log.debug("Searching for single task using {}", evaluator.getSearchWithElastic() ? "Elasticsearch" : "MongoDB"); if (evaluator.getSearchWithElastic()) { @@ -122,12 +119,9 @@ public Page searchAll(String queryString) { */ @Override public Page searchAll(QueryLangEvaluator evaluator) { - if (evaluator == null) { - throw new IllegalArgumentException("Query cannot be null"); - } - if (!evaluator.getMultiple()) { - throw new IllegalArgumentException("Cannot use searchAll() with a query that expects single result. Use searchOne() instead."); - } + checkEvaluatorNotNull(evaluator); + checkEvaluatorMultiplicity(evaluator); + checkEvaluatorResourceType(evaluator); log.debug("Searching for all tasks using {}", evaluator.getSearchWithElastic() ? "Elasticsearch" : "MongoDB"); if (evaluator.getSearchWithElastic()) { @@ -168,9 +162,9 @@ public long count(String queryString) { */ @Override public long count(QueryLangEvaluator evaluator) { - if (evaluator == null) { - throw new IllegalArgumentException("Query cannot be null"); - } + checkEvaluatorNotNull(evaluator); + checkEvaluatorResourceType(evaluator); + log.debug("Counting tasks using {}", evaluator.getSearchWithElastic() ? "Elasticsearch" : "MongoDB"); if (evaluator.getSearchWithElastic()) { log.trace("Executing Elasticsearch count query: {}", evaluator.getFullElasticQuery()); @@ -210,9 +204,9 @@ public boolean exists(String queryString) { */ @Override public boolean exists(QueryLangEvaluator evaluator) { - if (evaluator == null) { - throw new IllegalArgumentException("Query cannot be null"); - } + checkEvaluatorNotNull(evaluator); + checkEvaluatorResourceType(evaluator); + log.debug("Checking existence of tasks using {}", evaluator.getSearchWithElastic() ? "Elasticsearch" : "MongoDB"); if (evaluator.getSearchWithElastic()) { log.trace("Executing Elasticsearch exists query: {}", evaluator.getFullElasticQuery()); diff --git a/src/main/java/com/netgrif/application/engine/pfql/service/userresource/UserSearchService.java b/src/main/java/com/netgrif/application/engine/pfql/service/userresource/UserSearchService.java index 1bd8aa20ee9..5cdb9f60935 100644 --- a/src/main/java/com/netgrif/application/engine/pfql/service/userresource/UserSearchService.java +++ b/src/main/java/com/netgrif/application/engine/pfql/service/userresource/UserSearchService.java @@ -17,7 +17,7 @@ public class UserSearchService implements IResourceSearchService { // todo 2443 logging @Override - public QueryType getQueryType() { + public QueryType getQueryResourceType() { return QueryType.USER; } From 71e30d7926ba17f21fefba16b074d3f45f713971 Mon Sep 17 00:00:00 2001 From: chvostek Date: Fri, 5 Jun 2026 15:49:29 +0200 Subject: [PATCH 09/17] [NAE-2443] PFQL support - add user search API to ActionDelegate - implement authorization for process search - implement authorization for task search - implement authorization for case search - fix initialization of pageable in query - implement UserSearchService methods --- .../logic/action/ActionDelegate.groovy | 24 ++++++++ .../auth/service/AbstractUserService.java | 41 +++++++++++++ .../auth/service/interfaces/IUserService.java | 8 +++ .../petrinet/service/PetriNetService.java | 53 +++++++++++------ .../service/interfaces/IPetriNetService.java | 2 + .../engine/pfql/domain/antlr4/QueryLang.g4 | 2 +- .../pfql/service/IResourceSearchService.java | 17 ++++++ .../pfql/service/QueryLangEvaluator.java | 4 +- .../caseresource/CaseSearchService.java | 1 + .../processresource/ProcessSearchService.java | 10 ++-- .../taskresource/TaskSearchService.java | 1 + .../userresource/UserSearchService.java | 58 ++++++++++++++++--- .../service/LegacyCaseSearchService.java | 10 ++-- .../service/LegacyTaskSearchService.java | 8 ++- .../engine/workflow/service/TaskService.java | 32 +++++++--- .../workflow/service/WorkflowService.java | 26 ++++++--- 16 files changed, 240 insertions(+), 57 deletions(-) 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 e7f1cde0c26..6bba9e60dfa 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 @@ -52,6 +52,7 @@ import com.netgrif.application.engine.pfql.service.ISearchService import com.netgrif.application.engine.pfql.service.caseresource.CaseSearchService import com.netgrif.application.engine.pfql.service.processresource.ProcessSearchService import com.netgrif.application.engine.pfql.service.taskresource.TaskSearchService +import com.netgrif.application.engine.pfql.service.userresource.UserSearchService import com.netgrif.application.engine.rules.domain.RuleRepository import com.netgrif.application.engine.startup.DefaultFiltersRunner import com.netgrif.application.engine.startup.FilterRunner @@ -223,6 +224,9 @@ class ActionDelegate { @Autowired ProcessSearchService processSearchService + @Autowired + UserSearchService userSearchService + FrontendActionOutcome Frontend /** @@ -2752,6 +2756,26 @@ class ActionDelegate { return processSearchService.exists(query) } + IUser searchUser(String query) { + return userSearchService.searchOne(query) + } + + Page pagedSearchUsers(String query) { + return userSearchService.searchAll(query) + } + + List searchUsers(String query) { + return pagedSearchUsers(query).content + } + + long countUsers(String query) { + return userSearchService.count(query) + } + + boolean existsUser(String query) { + return userSearchService.exists(query) + } + Object search(String query) { Object result = searchService.search(query) if (result instanceof Page) { diff --git a/src/main/java/com/netgrif/application/engine/auth/service/AbstractUserService.java b/src/main/java/com/netgrif/application/engine/auth/service/AbstractUserService.java index d9e68491cf2..3074b6427d9 100644 --- a/src/main/java/com/netgrif/application/engine/auth/service/AbstractUserService.java +++ b/src/main/java/com/netgrif/application/engine/auth/service/AbstractUserService.java @@ -9,9 +9,11 @@ import com.netgrif.application.engine.petrinet.domain.roles.ProcessRole; import com.netgrif.application.engine.petrinet.service.interfaces.IProcessRoleService; import com.netgrif.application.engine.security.service.ISecurityContextService; +import com.querydsl.core.types.Predicate; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.data.domain.Page; import org.springframework.data.domain.PageImpl; +import org.springframework.data.domain.PageRequest; import org.springframework.data.domain.Pageable; import org.springframework.security.core.context.SecurityContextHolder; @@ -123,6 +125,45 @@ public IUser createSystemUser() { return system; } + @Override + public Page search(Predicate predicate, Pageable pageable) { + if (predicate == null) { + return Page.empty(); + } + if (pageable == null) { + pageable = Pageable.unpaged(); + } + return repository.findAll(predicate, pageable).map(IUser.class::cast); + } + + @Override + public IUser searchOne(Predicate predicate) { + if (predicate == null) { + return null; + } + Page userAsPage = repository.findAll(predicate, PageRequest.of(0, 1)); + if (userAsPage.getTotalElements() > 0) { + return userAsPage.getContent().get(0); + } + return null; + } + + @Override + public long count(Predicate predicate) { + if (predicate == null) { + return 0; + } + return repository.count(predicate); + } + + @Override + public boolean exists(Predicate predicate) { + if (predicate == null) { + return false; + } + return repository.exists(predicate); + } + public Page changeType(Page users, Pageable pageable) { return new PageImpl<>(changeType(users.getContent()), pageable, users.getTotalElements()); } diff --git a/src/main/java/com/netgrif/application/engine/auth/service/interfaces/IUserService.java b/src/main/java/com/netgrif/application/engine/auth/service/interfaces/IUserService.java index 989062f7585..6eb08971899 100644 --- a/src/main/java/com/netgrif/application/engine/auth/service/interfaces/IUserService.java +++ b/src/main/java/com/netgrif/application/engine/auth/service/interfaces/IUserService.java @@ -5,6 +5,7 @@ import com.netgrif.application.engine.auth.domain.LoggedUser; import com.netgrif.application.engine.auth.web.requestbodies.UpdateUserRequest; import com.netgrif.application.engine.petrinet.domain.PetriNet; +import com.querydsl.core.types.Predicate; import org.bson.types.ObjectId; import org.springframework.data.domain.Page; import org.springframework.data.domain.Pageable; @@ -77,4 +78,11 @@ public interface IUserService { IUser createSystemUser(); + Page search(Predicate predicate, Pageable pageable); + + IUser searchOne(Predicate predicate); + + long count(Predicate predicate); + + boolean exists(Predicate predicate); } diff --git a/src/main/java/com/netgrif/application/engine/petrinet/service/PetriNetService.java b/src/main/java/com/netgrif/application/engine/petrinet/service/PetriNetService.java index e182f0d7481..1c9aa2b5868 100644 --- a/src/main/java/com/netgrif/application/engine/petrinet/service/PetriNetService.java +++ b/src/main/java/com/netgrif/application/engine/petrinet/service/PetriNetService.java @@ -15,10 +15,7 @@ import com.netgrif.application.engine.importer.service.throwable.MissingIconKeyException; import com.netgrif.application.engine.ldap.service.interfaces.ILdapGroupRefService; import com.netgrif.application.engine.orgstructure.groups.interfaces.INextGroupService; -import com.netgrif.application.engine.petrinet.domain.PetriNet; -import com.netgrif.application.engine.petrinet.domain.PetriNetSearch; -import com.netgrif.application.engine.petrinet.domain.Transition; -import com.netgrif.application.engine.petrinet.domain.VersionType; +import com.netgrif.application.engine.petrinet.domain.*; import com.netgrif.application.engine.petrinet.domain.dataset.logic.action.Action; import com.netgrif.application.engine.petrinet.domain.dataset.logic.action.FieldActionsRunner; import com.netgrif.application.engine.petrinet.domain.events.EventPhase; @@ -38,6 +35,8 @@ import com.netgrif.application.engine.workflow.service.interfaces.IEventService; import com.netgrif.application.engine.workflow.service.interfaces.IFieldActionsCacheService; import com.netgrif.application.engine.workflow.service.interfaces.IWorkflowService; +import com.querydsl.core.BooleanBuilder; +import com.querydsl.core.types.ExpressionUtils; import com.querydsl.core.types.Predicate; import lombok.extern.slf4j.Slf4j; import org.apache.tomcat.util.http.fileupload.IOUtils; @@ -525,32 +524,44 @@ public Page search(PetriNetSearch criteriaClass, LoggedUser u @Override public Page search(Predicate predicate, Pageable pageable) { + if (predicate == null) { + return Page.empty(); + } if (pageable == null) { pageable = Pageable.unpaged(); } - if (predicate != null) { - // todo 2443 logged user permissions - return repository.findAll(predicate, pageable); + Predicate permissionConstraint = getProcessRolesPredicate(userService.getLoggedOrSystem().transformToLoggedUser()); + Predicate finalPredicate = ExpressionUtils.and(predicate, permissionConstraint); + return repository.findAll(finalPredicate, pageable); + } + + @Override + public PetriNet searchOne(Predicate predicate) { + Page processAsPage = search(predicate, PageRequest.of(0, 1)); + if (processAsPage.getTotalElements() > 0) { + return processAsPage.getContent().get(0); } - return Page.empty(); + return null; } @Override public long count(Predicate predicate) { - if (predicate != null) { - // todo 2443 logged user permissions - return repository.count(predicate); + if (predicate == null) { + return 0; } - return 0; + Predicate permissionConstraint = getProcessRolesPredicate(userService.getLoggedOrSystem().transformToLoggedUser()); + Predicate finalPredicate = ExpressionUtils.and(predicate, permissionConstraint); + return repository.count(finalPredicate); } @Override public boolean exists(Predicate predicate) { - if (predicate != null) { - // todo 2443 logged user permissions - return repository.exists(predicate); + if (predicate == null) { + return false; } - return false; + Predicate permissionConstraint = getProcessRolesPredicate(userService.getLoggedOrSystem().transformToLoggedUser()); + Predicate finalPredicate = ExpressionUtils.and(predicate, permissionConstraint); + return repository.exists(finalPredicate); } private void addValueCriteria(Query query, Query queryTotal, Criteria criteria) { @@ -562,7 +573,7 @@ private void addValueCriteria(Query query, Query queryTotal, Criteria criteria) @Transactional public void deletePetriNet(String processId, LoggedUser loggedUser) { Optional petriNetOptional = repository.findById(processId); - if (!petriNetOptional.isPresent()) { + if (petriNetOptional.isEmpty()) { throw new IllegalArgumentException("Could not find process with id [" + processId + "]"); } @@ -594,6 +605,14 @@ private Criteria getProcessRolesCriteria(LoggedUser user) { .map(role -> Criteria.where("permissions." + role).exists(true)).toArray(Criteria[]::new)); } + private Predicate getProcessRolesPredicate(LoggedUser user) { + BooleanBuilder result = new BooleanBuilder(); + user.getProcessRoles().forEach(role -> { + result.or(QPetriNet.petriNet.permissions.containsKey(role)); + }); + return result; + } + @Override public void runActions(List actions, PetriNet petriNet) { log.info("Running actions of net [" + petriNet.getStringId() + "]"); diff --git a/src/main/java/com/netgrif/application/engine/petrinet/service/interfaces/IPetriNetService.java b/src/main/java/com/netgrif/application/engine/petrinet/service/interfaces/IPetriNetService.java index 53b1a4a013f..5d94ac5d539 100644 --- a/src/main/java/com/netgrif/application/engine/petrinet/service/interfaces/IPetriNetService.java +++ b/src/main/java/com/netgrif/application/engine/petrinet/service/interfaces/IPetriNetService.java @@ -94,6 +94,8 @@ static DataFieldReference transformToReference(PetriNet net, Transition transiti Page search(Predicate predicate, Pageable pageable); + PetriNet searchOne(Predicate predicate); + long count(Predicate predicate); boolean exists(Predicate predicate); diff --git a/src/main/java/com/netgrif/application/engine/pfql/domain/antlr4/QueryLang.g4 b/src/main/java/com/netgrif/application/engine/pfql/domain/antlr4/QueryLang.g4 index cfb134ff2ee..b2fba1623e2 100644 --- a/src/main/java/com/netgrif/application/engine/pfql/domain/antlr4/QueryLang.g4 +++ b/src/main/java/com/netgrif/application/engine/pfql/domain/antlr4/QueryLang.g4 @@ -1,4 +1,4 @@ -// todo 2443 generate this with plugin +// todo generate this with plugin grammar QueryLang; query: resource=(PROCESS | PROCESSES) delimeter processConditions (paging)? (processSorting)? EOF # processQuery diff --git a/src/main/java/com/netgrif/application/engine/pfql/service/IResourceSearchService.java b/src/main/java/com/netgrif/application/engine/pfql/service/IResourceSearchService.java index 2271645c93c..6461bf4c13e 100644 --- a/src/main/java/com/netgrif/application/engine/pfql/service/IResourceSearchService.java +++ b/src/main/java/com/netgrif/application/engine/pfql/service/IResourceSearchService.java @@ -2,7 +2,11 @@ import com.netgrif.application.engine.pfql.domain.enums.QueryType; +import org.slf4j.Logger; import org.springframework.data.domain.Page; +import org.springframework.data.domain.PageRequest; +import org.springframework.data.domain.Pageable; + /** * Service interface for searching resources using query language expressions. @@ -36,6 +40,10 @@ public interface IResourceSearchService { boolean exists(String queryString); boolean exists(QueryLangEvaluator evaluator); + static Pageable getDefaultPageable() { + return PageRequest.ofSize(100); + } + // todo 2443 javadoc default void checkEvaluatorNotNull(QueryLangEvaluator evaluator) { if (evaluator == null) { @@ -43,6 +51,15 @@ default void checkEvaluatorNotNull(QueryLangEvaluator evaluator) { } } + // todo 2443 javadoc + default void updateWithDefaultPageableIfMissing(QueryLangEvaluator evaluator, Logger logger) { + if (evaluator.getPageable() == null) { + Pageable pageable = getDefaultPageable(); + logger.debug("Pageable was missing. Using default pageable: {}", pageable); + evaluator.setPageable(getDefaultPageable()); + } + } + // todo 2443 javadoc default void checkEvaluatorMultiplicity(QueryLangEvaluator evaluator) { if (evaluator.getMultiple()) { diff --git a/src/main/java/com/netgrif/application/engine/pfql/service/QueryLangEvaluator.java b/src/main/java/com/netgrif/application/engine/pfql/service/QueryLangEvaluator.java index f661b9a35a4..feba51bf24e 100644 --- a/src/main/java/com/netgrif/application/engine/pfql/service/QueryLangEvaluator.java +++ b/src/main/java/com/netgrif/application/engine/pfql/service/QueryLangEvaluator.java @@ -35,8 +35,7 @@ public class QueryLangEvaluator extends QueryLangBaseListener { - // todo 2443 review - // todo 2443 split code into dedicated evaluators + // todo code encapsulation by resource type private final ParseTreeProperty elasticQuery = new ParseTreeProperty<>(); private final ParseTreeProperty mongoQuery = new ParseTreeProperty<>(); @@ -53,6 +52,7 @@ public class QueryLangEvaluator extends QueryLangBaseListener { @Getter private String fullElasticQuery; @Getter + @Setter private Pageable pageable; private int pageNumber = 0; diff --git a/src/main/java/com/netgrif/application/engine/pfql/service/caseresource/CaseSearchService.java b/src/main/java/com/netgrif/application/engine/pfql/service/caseresource/CaseSearchService.java index 710c0914394..a4e52611014 100644 --- a/src/main/java/com/netgrif/application/engine/pfql/service/caseresource/CaseSearchService.java +++ b/src/main/java/com/netgrif/application/engine/pfql/service/caseresource/CaseSearchService.java @@ -113,6 +113,7 @@ public Page searchAll(QueryLangEvaluator evaluator) { checkEvaluatorNotNull(evaluator); checkEvaluatorMultiplicity(evaluator); checkEvaluatorResourceType(evaluator); + updateWithDefaultPageableIfMissing(evaluator, log); log.debug("Searching for all cases using {} with pagination: page={}, size={}", evaluator.getSearchWithElastic() ? "Elasticsearch" : "MongoDB", diff --git a/src/main/java/com/netgrif/application/engine/pfql/service/processresource/ProcessSearchService.java b/src/main/java/com/netgrif/application/engine/pfql/service/processresource/ProcessSearchService.java index af218bb683f..3728154065b 100644 --- a/src/main/java/com/netgrif/application/engine/pfql/service/processresource/ProcessSearchService.java +++ b/src/main/java/com/netgrif/application/engine/pfql/service/processresource/ProcessSearchService.java @@ -71,10 +71,9 @@ public PetriNet searchOne(QueryLangEvaluator evaluator) { log.debug("Searching for single process using MongoDB"); log.trace("Executing MongoDB query: {}", evaluator.getFullMongoQuery()); - Page processAsPage = petriNetService.search(evaluator.getFullMongoQuery(), PageRequest.of(0, 1)); - Optional processOpt = processAsPage.getContent().stream().findFirst(); - log.trace("MongoDB search one result: {}", processOpt.isPresent() ? processOpt.get().getStringId() : "null"); - return processOpt.orElse(null); + PetriNet result = petriNetService.searchOne(evaluator.getFullMongoQuery()); + log.trace("MongoDB search one result: {}", result != null ? result.getStringId() : "null"); + return result; } /** @@ -111,7 +110,8 @@ public Page searchAll(QueryLangEvaluator evaluator) { checkEvaluatorNotNull(evaluator); checkEvaluatorMultiplicity(evaluator); checkEvaluatorResourceType(evaluator); - + updateWithDefaultPageableIfMissing(evaluator, log); + // todo implement Elasticsearch search (service layer and evaluator layer) log.debug("Searching for all processes using MongoDB"); diff --git a/src/main/java/com/netgrif/application/engine/pfql/service/taskresource/TaskSearchService.java b/src/main/java/com/netgrif/application/engine/pfql/service/taskresource/TaskSearchService.java index 2a49a3af218..feb987ab548 100644 --- a/src/main/java/com/netgrif/application/engine/pfql/service/taskresource/TaskSearchService.java +++ b/src/main/java/com/netgrif/application/engine/pfql/service/taskresource/TaskSearchService.java @@ -122,6 +122,7 @@ public Page searchAll(QueryLangEvaluator evaluator) { checkEvaluatorNotNull(evaluator); checkEvaluatorMultiplicity(evaluator); checkEvaluatorResourceType(evaluator); + updateWithDefaultPageableIfMissing(evaluator, log); log.debug("Searching for all tasks using {}", evaluator.getSearchWithElastic() ? "Elasticsearch" : "MongoDB"); if (evaluator.getSearchWithElastic()) { diff --git a/src/main/java/com/netgrif/application/engine/pfql/service/userresource/UserSearchService.java b/src/main/java/com/netgrif/application/engine/pfql/service/userresource/UserSearchService.java index 5cdb9f60935..d08fe712a9b 100644 --- a/src/main/java/com/netgrif/application/engine/pfql/service/userresource/UserSearchService.java +++ b/src/main/java/com/netgrif/application/engine/pfql/service/userresource/UserSearchService.java @@ -1,6 +1,7 @@ package com.netgrif.application.engine.pfql.service.userresource; import com.netgrif.application.engine.auth.domain.IUser; +import com.netgrif.application.engine.auth.service.interfaces.IUserService; import com.netgrif.application.engine.pfql.domain.enums.QueryType; import com.netgrif.application.engine.pfql.service.IResourceSearchService; import com.netgrif.application.engine.pfql.service.QueryLangEvaluator; @@ -9,12 +10,15 @@ import org.springframework.data.domain.Page; import org.springframework.stereotype.Service; +import static com.netgrif.application.engine.pfql.service.utils.SearchUtils.evaluateQuery; + @Slf4j @Service @RequiredArgsConstructor public class UserSearchService implements IResourceSearchService { + + private final IUserService userService; // todo 2443 javadoc - // todo 2443 logging @Override public QueryType getQueryResourceType() { @@ -23,41 +27,77 @@ public QueryType getQueryResourceType() { @Override public IUser searchOne(String queryString) { - return null; + log.debug("Searching for single user with query: {}", queryString); + return searchOne(evaluateQuery(queryString)); } @Override public IUser searchOne(QueryLangEvaluator evaluator) { - return null; + checkEvaluatorNotNull(evaluator); + checkEvaluatorMultiplicity(evaluator); + checkEvaluatorResourceType(evaluator); + + log.debug("Searching for single user using MongoDB"); + log.trace("Executing MongoDB query: {}", evaluator.getFullMongoQuery()); + IUser result = userService.searchOne(evaluator.getFullMongoQuery()); + log.trace("MongoDB search one result: {}", result != null ? result.getStringId() : "null"); + return result; } @Override public Page searchAll(String queryString) { - return null; + log.debug("Searching for all users with query: {}", queryString); + return searchAll(evaluateQuery(queryString)); } @Override public Page searchAll(QueryLangEvaluator evaluator) { - return null; + checkEvaluatorNotNull(evaluator); + checkEvaluatorMultiplicity(evaluator); + checkEvaluatorResourceType(evaluator); + updateWithDefaultPageableIfMissing(evaluator, log); + + log.debug("Searching for all users using MongoDB with pagination: page={}, size={}", + evaluator.getPageable().getPageNumber(), evaluator.getPageable().getPageSize()); + log.trace("Executing MongoDB query: {}", evaluator.getFullMongoQuery()); + Page result = userService.search(evaluator.getFullMongoQuery(), evaluator.getPageable()); + log.trace("MongoDB search all result: page size={}, total elements={}", result.getNumberOfElements(), result.getTotalElements()); + return result; } @Override public long count(String queryString) { - return 0; + log.debug("Counting users with query: {}", queryString); + return count(evaluateQuery(queryString)); } @Override public long count(QueryLangEvaluator evaluator) { - return 0; + checkEvaluatorNotNull(evaluator); + checkEvaluatorResourceType(evaluator); + + log.debug("Counting users using MongoDB"); + log.trace("Executing MongoDB count query: {}", evaluator.getFullMongoQuery()); + long result = userService.count(evaluator.getFullMongoQuery()); + log.trace("MongoDB count result: {}", result); + return result; } @Override public boolean exists(String queryString) { - return false; + log.debug("Checking existence of user with query: {}", queryString); + return exists(evaluateQuery(queryString)); } @Override public boolean exists(QueryLangEvaluator evaluator) { - return false; + checkEvaluatorNotNull(evaluator); + checkEvaluatorResourceType(evaluator); + + log.debug("Checking existence of users using MongoDB"); + log.trace("Executing MongoDB exists query: {}", evaluator.getFullMongoQuery()); + boolean result = userService.exists(evaluator.getFullMongoQuery()); + log.trace("MongoDB exists result: {}", result); + return result; } } diff --git a/src/main/java/com/netgrif/application/engine/workflow/service/LegacyCaseSearchService.java b/src/main/java/com/netgrif/application/engine/workflow/service/LegacyCaseSearchService.java index 9d07ca2821f..184fd2e91ca 100644 --- a/src/main/java/com/netgrif/application/engine/workflow/service/LegacyCaseSearchService.java +++ b/src/main/java/com/netgrif/application/engine/workflow/service/LegacyCaseSearchService.java @@ -33,8 +33,6 @@ @Service public class LegacyCaseSearchService extends MongoSearchService { - // todo 2443 remove? - private static final Logger log = LoggerFactory.getLogger(LegacyCaseSearchService.class.getName()); public static final String ROLE = "role"; @@ -91,12 +89,16 @@ public Predicate buildQuery(Map requestQuery, LoggedUser user, L return null; } } + builder.and(buildPermissionConstraints(loggedOrImpersonated)); + return builder; + } + + public BooleanBuilder buildPermissionConstraints(LoggedUser loggedOrImpersonated) { BooleanBuilder permissionConstraints = new BooleanBuilder(buildViewRoleQueryConstraint(loggedOrImpersonated)); permissionConstraints.andNot(buildNegativeViewRoleQueryConstraint(loggedOrImpersonated)); permissionConstraints.or(buildViewUserQueryConstraint(loggedOrImpersonated)); permissionConstraints.andNot(buildNegativeViewUsersQueryConstraint(loggedOrImpersonated)); - builder.and(permissionConstraints); - return builder; + return permissionConstraints; } protected Predicate buildViewRoleQueryConstraint(LoggedUser user) { diff --git a/src/main/java/com/netgrif/application/engine/workflow/service/LegacyTaskSearchService.java b/src/main/java/com/netgrif/application/engine/workflow/service/LegacyTaskSearchService.java index e4dec1184ca..48ece227576 100644 --- a/src/main/java/com/netgrif/application/engine/workflow/service/LegacyTaskSearchService.java +++ b/src/main/java/com/netgrif/application/engine/workflow/service/LegacyTaskSearchService.java @@ -45,12 +45,16 @@ public Predicate buildQuery(List requests, LoggedUser user, L constraints.or(buildUserRefQueryConstraint(loggedOrImpersonated)); builder.and(constraints); + builder.and(buildPermissionConstraints(loggedOrImpersonated)); + return builder; + } + + public BooleanBuilder buildPermissionConstraints(LoggedUser loggedOrImpersonated) { BooleanBuilder permissionConstraints = new BooleanBuilder(buildViewRoleQueryConstraint(loggedOrImpersonated)); permissionConstraints.andNot(buildNegativeViewRoleQueryConstraint(loggedOrImpersonated)); permissionConstraints.or(buildViewUserQueryConstraint(loggedOrImpersonated)); permissionConstraints.andNot(buildNegativeViewUsersQueryConstraint(loggedOrImpersonated)); - builder.and(permissionConstraints); - return builder; + return permissionConstraints; } protected Predicate buildRolesQueryConstraint(LoggedUser user) { diff --git a/src/main/java/com/netgrif/application/engine/workflow/service/TaskService.java b/src/main/java/com/netgrif/application/engine/workflow/service/TaskService.java index 6747c0700a3..6eacccb3150 100644 --- a/src/main/java/com/netgrif/application/engine/workflow/service/TaskService.java +++ b/src/main/java/com/netgrif/application/engine/workflow/service/TaskService.java @@ -44,6 +44,7 @@ import com.netgrif.application.engine.workflow.service.interfaces.IWorkflowService; import com.netgrif.application.engine.workflow.web.requestbodies.TaskSearchRequest; import com.netgrif.application.engine.workflow.web.responsebodies.TaskReference; +import com.querydsl.core.types.ExpressionUtils; import lombok.extern.slf4j.Slf4j; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.beans.factory.annotation.Qualifier; @@ -692,14 +693,25 @@ public long count(List requests, LoggedUser user, Locale loca @Override public long count(com.querydsl.core.types.Predicate predicate) { - // todo 2443 logged user permissions - return predicate != null ? taskRepository.count(predicate) : 0; + if (predicate == null) { + return 0; + } + + com.querydsl.core.types.Predicate permissionConstraints = searchService.buildPermissionConstraints( + userService.getLoggedOrSystem().transformToLoggedUser()); + com.querydsl.core.types.Predicate finalPredicate = ExpressionUtils.and(predicate, permissionConstraints); + return taskRepository.count(finalPredicate); } @Override public boolean exists(com.querydsl.core.types.Predicate predicate) { - // todo 2443 logged user permissions - return predicate != null && taskRepository.exists(predicate); + if (predicate == null) { + return false; + } + com.querydsl.core.types.Predicate permissionConstraints = searchService.buildPermissionConstraints( + userService.getLoggedOrSystem().transformToLoggedUser()); + com.querydsl.core.types.Predicate finalPredicate = ExpressionUtils.and(predicate, permissionConstraints); + return taskRepository.exists(finalPredicate); } @Override @@ -745,15 +757,19 @@ public Page searchAll(com.querydsl.core.types.Predicate predicate) { @Override public Page search(com.querydsl.core.types.Predicate predicate, Pageable pageable) { - // todo 2443 logged user permissions - Page tasks = taskRepository.findAll(predicate, pageable); + if (predicate == null) { + return Page.empty(); + } + com.querydsl.core.types.Predicate permissionConstraints = searchService.buildPermissionConstraints( + userService.getLoggedOrSystem().transformToLoggedUser()); + com.querydsl.core.types.Predicate finalPredicate = ExpressionUtils.and(predicate, permissionConstraints); + Page tasks = taskRepository.findAll(finalPredicate, pageable); return loadUsers(tasks); } @Override public Task searchOne(com.querydsl.core.types.Predicate predicate) { - // todo 2443 logged user permissions - Page tasks = taskRepository.findAll(predicate, PageRequest.of(0, 1)); + Page tasks = search(predicate, PageRequest.of(0, 1)); if (tasks.getTotalElements() > 0) { loadUsers(tasks); return tasks.getContent().get(0); diff --git a/src/main/java/com/netgrif/application/engine/workflow/service/WorkflowService.java b/src/main/java/com/netgrif/application/engine/workflow/service/WorkflowService.java index a38de6a39e7..b33c5e648e8 100644 --- a/src/main/java/com/netgrif/application/engine/workflow/service/WorkflowService.java +++ b/src/main/java/com/netgrif/application/engine/workflow/service/WorkflowService.java @@ -30,6 +30,7 @@ import com.netgrif.application.engine.workflow.service.interfaces.IInitValueExpressionEvaluator; import com.netgrif.application.engine.workflow.service.interfaces.ITaskService; import com.netgrif.application.engine.workflow.service.interfaces.IWorkflowService; +import com.querydsl.core.types.ExpressionUtils; import com.querydsl.core.types.Predicate; import org.bson.types.ObjectId; import org.slf4j.Logger; @@ -185,7 +186,12 @@ public Page findAllByUri(String uri, Pageable pageable) { @Override public Page search(Predicate predicate, Pageable pageable) { - Page page = repository.findAll(predicate, pageable); + if (predicate == null) { + return Page.empty(); + } + Predicate permissionConstraints = searchService.buildPermissionConstraints(userService.getLoggedOrSystem().transformToLoggedUser()); + Predicate finalPredicate = ExpressionUtils.and(predicate, permissionConstraints); + Page page = repository.findAll(finalPredicate, pageable); page.getContent().forEach(this::setPetriNet); decryptDataSets(page.getContent()); return setImmediateDataFields(page); @@ -217,22 +223,24 @@ public long count(Map request, LoggedUser user, Locale locale) { @Override public long count(Predicate predicate) { - // todo 2443 logged user permissions - if (predicate != null) { - return repository.count(predicate); - } else { + if (predicate == null) { return 0; } + + Predicate permissionConstraints = searchService.buildPermissionConstraints(userService.getLoggedOrSystem().transformToLoggedUser()); + Predicate finalPredicate = ExpressionUtils.and(predicate, permissionConstraints); + return repository.count(finalPredicate); } @Override public boolean exists(Predicate predicate) { - // todo 2443 logged user permissions - if (predicate != null) { - return repository.exists(predicate); - } else { + if (predicate == null) { return false; } + + Predicate permissionConstraints = searchService.buildPermissionConstraints(userService.getLoggedOrSystem().transformToLoggedUser()); + Predicate finalPredicate = ExpressionUtils.and(predicate, permissionConstraints); + return repository.exists(finalPredicate); } @Override From 46e839b17b53bc49e4bcf2b32469d103d902c414 Mon Sep 17 00:00:00 2001 From: chvostek Date: Fri, 5 Jun 2026 15:59:57 +0200 Subject: [PATCH 10/17] [NAE-2443] PFQL support - fix validation --- .../engine/pfql/service/IResourceSearchService.java | 9 ++++++++- .../pfql/service/caseresource/CaseSearchService.java | 4 ++-- .../service/processresource/ProcessSearchService.java | 4 ++-- .../pfql/service/taskresource/TaskSearchService.java | 4 ++-- .../pfql/service/userresource/UserSearchService.java | 4 ++-- 5 files changed, 16 insertions(+), 9 deletions(-) diff --git a/src/main/java/com/netgrif/application/engine/pfql/service/IResourceSearchService.java b/src/main/java/com/netgrif/application/engine/pfql/service/IResourceSearchService.java index 6461bf4c13e..187bf7157d0 100644 --- a/src/main/java/com/netgrif/application/engine/pfql/service/IResourceSearchService.java +++ b/src/main/java/com/netgrif/application/engine/pfql/service/IResourceSearchService.java @@ -61,7 +61,14 @@ default void updateWithDefaultPageableIfMissing(QueryLangEvaluator evaluator, Lo } // todo 2443 javadoc - default void checkEvaluatorMultiplicity(QueryLangEvaluator evaluator) { + default void checkEvaluatorIsMultiple(QueryLangEvaluator evaluator) { + if (!evaluator.getMultiple()) { + throw new IllegalArgumentException("Cannot use searchAll() with a query that expects single result. Use searchOne() instead."); + } + } + + // todo 2443 javadoc + default void checkEvaluatorIsSingle(QueryLangEvaluator evaluator) { if (evaluator.getMultiple()) { throw new IllegalArgumentException("Cannot use searchOne() with a query that expects multiple results. Use searchAll() instead."); } diff --git a/src/main/java/com/netgrif/application/engine/pfql/service/caseresource/CaseSearchService.java b/src/main/java/com/netgrif/application/engine/pfql/service/caseresource/CaseSearchService.java index a4e52611014..49edc893e21 100644 --- a/src/main/java/com/netgrif/application/engine/pfql/service/caseresource/CaseSearchService.java +++ b/src/main/java/com/netgrif/application/engine/pfql/service/caseresource/CaseSearchService.java @@ -68,7 +68,7 @@ public Case searchOne(String queryString) { @Override public Case searchOne(QueryLangEvaluator evaluator) { checkEvaluatorNotNull(evaluator); - checkEvaluatorMultiplicity(evaluator); + checkEvaluatorIsSingle(evaluator); checkEvaluatorResourceType(evaluator); log.debug("Searching for single case using {}", evaluator.getSearchWithElastic() ? "Elasticsearch" : "MongoDB"); @@ -111,7 +111,7 @@ public Page searchAll(String queryString) { @Override public Page searchAll(QueryLangEvaluator evaluator) { checkEvaluatorNotNull(evaluator); - checkEvaluatorMultiplicity(evaluator); + checkEvaluatorIsMultiple(evaluator); checkEvaluatorResourceType(evaluator); updateWithDefaultPageableIfMissing(evaluator, log); diff --git a/src/main/java/com/netgrif/application/engine/pfql/service/processresource/ProcessSearchService.java b/src/main/java/com/netgrif/application/engine/pfql/service/processresource/ProcessSearchService.java index 3728154065b..3634b3dd3e3 100644 --- a/src/main/java/com/netgrif/application/engine/pfql/service/processresource/ProcessSearchService.java +++ b/src/main/java/com/netgrif/application/engine/pfql/service/processresource/ProcessSearchService.java @@ -64,7 +64,7 @@ public PetriNet searchOne(String queryString) { @Override public PetriNet searchOne(QueryLangEvaluator evaluator) { checkEvaluatorNotNull(evaluator); - checkEvaluatorMultiplicity(evaluator); + checkEvaluatorIsSingle(evaluator); checkEvaluatorResourceType(evaluator); // todo implement Elasticsearch search (service layer and evaluator layer) @@ -108,7 +108,7 @@ public Page searchAll(String queryString) { @Override public Page searchAll(QueryLangEvaluator evaluator) { checkEvaluatorNotNull(evaluator); - checkEvaluatorMultiplicity(evaluator); + checkEvaluatorIsMultiple(evaluator); checkEvaluatorResourceType(evaluator); updateWithDefaultPageableIfMissing(evaluator, log); diff --git a/src/main/java/com/netgrif/application/engine/pfql/service/taskresource/TaskSearchService.java b/src/main/java/com/netgrif/application/engine/pfql/service/taskresource/TaskSearchService.java index feb987ab548..9981c11f6ae 100644 --- a/src/main/java/com/netgrif/application/engine/pfql/service/taskresource/TaskSearchService.java +++ b/src/main/java/com/netgrif/application/engine/pfql/service/taskresource/TaskSearchService.java @@ -75,7 +75,7 @@ public Task searchOne(String queryString) { @Override public Task searchOne(QueryLangEvaluator evaluator) { checkEvaluatorNotNull(evaluator); - checkEvaluatorMultiplicity(evaluator); + checkEvaluatorIsSingle(evaluator); checkEvaluatorResourceType(evaluator); log.debug("Searching for single task using {}", evaluator.getSearchWithElastic() ? "Elasticsearch" : "MongoDB"); @@ -120,7 +120,7 @@ public Page searchAll(String queryString) { @Override public Page searchAll(QueryLangEvaluator evaluator) { checkEvaluatorNotNull(evaluator); - checkEvaluatorMultiplicity(evaluator); + checkEvaluatorIsMultiple(evaluator); checkEvaluatorResourceType(evaluator); updateWithDefaultPageableIfMissing(evaluator, log); diff --git a/src/main/java/com/netgrif/application/engine/pfql/service/userresource/UserSearchService.java b/src/main/java/com/netgrif/application/engine/pfql/service/userresource/UserSearchService.java index d08fe712a9b..4c6b65fa2c5 100644 --- a/src/main/java/com/netgrif/application/engine/pfql/service/userresource/UserSearchService.java +++ b/src/main/java/com/netgrif/application/engine/pfql/service/userresource/UserSearchService.java @@ -34,7 +34,7 @@ public IUser searchOne(String queryString) { @Override public IUser searchOne(QueryLangEvaluator evaluator) { checkEvaluatorNotNull(evaluator); - checkEvaluatorMultiplicity(evaluator); + checkEvaluatorIsSingle(evaluator); checkEvaluatorResourceType(evaluator); log.debug("Searching for single user using MongoDB"); @@ -53,7 +53,7 @@ public Page searchAll(String queryString) { @Override public Page searchAll(QueryLangEvaluator evaluator) { checkEvaluatorNotNull(evaluator); - checkEvaluatorMultiplicity(evaluator); + checkEvaluatorIsMultiple(evaluator); checkEvaluatorResourceType(evaluator); updateWithDefaultPageableIfMissing(evaluator, log); From a187796bc1b2f9d8021561c0cd0d7ecd15fed0f9 Mon Sep 17 00:00:00 2001 From: chvostek Date: Mon, 8 Jun 2026 09:08:49 +0200 Subject: [PATCH 11/17] [NAE-2443] PFQL support - implement QueryLangActionTest - move some tests to java package --- .../engine/pfql/QueryLangActionTest.groovy | 160 ++++++++++++ .../engine/pfql/QueryLangTest.java | 0 .../engine/pfql/utils/MongoDbUtils.java | 0 .../engine/pfql/utils/SearchTestUtils.java | 0 .../resources/petriNets/query_lang_test.xml | 246 ++++++++++++++++++ 5 files changed, 406 insertions(+) create mode 100644 src/test/groovy/com/netgrif/application/engine/pfql/QueryLangActionTest.groovy rename src/test/{groovy => java}/com/netgrif/application/engine/pfql/QueryLangTest.java (100%) rename src/test/{groovy => java}/com/netgrif/application/engine/pfql/utils/MongoDbUtils.java (100%) rename src/test/{groovy => java}/com/netgrif/application/engine/pfql/utils/SearchTestUtils.java (100%) create mode 100644 src/test/resources/petriNets/query_lang_test.xml diff --git a/src/test/groovy/com/netgrif/application/engine/pfql/QueryLangActionTest.groovy b/src/test/groovy/com/netgrif/application/engine/pfql/QueryLangActionTest.groovy new file mode 100644 index 00000000000..b5246cca2ec --- /dev/null +++ b/src/test/groovy/com/netgrif/application/engine/pfql/QueryLangActionTest.groovy @@ -0,0 +1,160 @@ +package com.netgrif.application.engine.pfql + +import com.netgrif.application.engine.TestHelper +import com.netgrif.application.engine.petrinet.domain.PetriNet +import com.netgrif.application.engine.petrinet.domain.dataset.FieldType +import com.netgrif.application.engine.startup.ImportHelper +import com.netgrif.application.engine.startup.SuperCreator +import com.netgrif.application.engine.workflow.domain.Case +import com.netgrif.application.engine.workflow.service.interfaces.IDataService +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 static org.junit.jupiter.api.Assertions.assertEquals +import static org.junit.jupiter.api.Assertions.assertThrows + +@Slf4j +@SpringBootTest +@ActiveProfiles(["test"]) +@ExtendWith(SpringExtension.class) +class QueryLangActionTest { + + private static final String TRANS_ID = "t1" + private static final String QUERY_FIELD_ID = "input_query" + private static final String RESULT_FIELD_ID = "result" + private static final String RESULT_CLASS_FIELD_ID = "result_class" + private static final String SEARCH_GENERAL_SEARCH_FIELD_ID = "general_search" + private static final String SEARCH_GENERAL_COUNT_FIELD_ID = "general_count" + private static final String SEARCH_GENERAL_EXISTS_FIELD_ID = "general_exists" + + private static final String SEARCH_ONE_TEMPLATE = "%s_search_one" + private static final String SEARCH_TEMPLATE = "%s_search" + private static final String SEARCH_PAGED_TEMPLATE = "%s_paged_search" + + @Autowired + private TestHelper testHelper + + @Autowired + private ImportHelper importHelper + + @Autowired + private IDataService dataService + + @Autowired + private SuperCreator superCreator + + private PetriNet testProcess + private Case testCase + private String testCaseTaskId + + @BeforeEach + void beforeEach() { + testHelper.truncateDbs() + testProcess = importHelper.createNet("/query_lang_test.xml").get() + testCase = importHelper.createCase("test", testProcess) + testCaseTaskId = testCase.tasks.find { taskPair -> taskPair.transition == TRANS_ID }.task + } + + @Test + void generalTest() { + updateQuery("process: identifier == '" + testProcess.identifier + "'") + + pressButton(SEARCH_GENERAL_EXISTS_FIELD_ID) + assertEquals(true, testCase.getFieldValue(RESULT_FIELD_ID)) + + pressButton(SEARCH_GENERAL_COUNT_FIELD_ID) + assertEquals(1, testCase.getFieldValue(RESULT_FIELD_ID)) + + pressButton(SEARCH_GENERAL_SEARCH_FIELD_ID) + assertEquals("class com.netgrif.application.engine.petrinet.domain.PetriNet", testCase.getFieldValue(RESULT_CLASS_FIELD_ID)) + updateQuery("processes: identifier == '" + testProcess.identifier + "'") + pressButton(SEARCH_GENERAL_SEARCH_FIELD_ID) + assertEquals("class java.util.Collections\$UnmodifiableRandomAccessList", testCase.getFieldValue(RESULT_CLASS_FIELD_ID)) + } + + @Test + void processTest() { + updateQuery("process: identifier == '" + testProcess.identifier + "'") + pressButton(String.format(SEARCH_ONE_TEMPLATE, "process")) + assertEquals("class com.netgrif.application.engine.petrinet.domain.PetriNet", testCase.getFieldValue(RESULT_CLASS_FIELD_ID)) + + updateQuery("processes: identifier == '" + testProcess.identifier + "'") + pressButton(String.format(SEARCH_TEMPLATE, "process")) + assertEquals("class java.util.Collections\$UnmodifiableRandomAccessList", testCase.getFieldValue(RESULT_CLASS_FIELD_ID)) + + pressButton(String.format(SEARCH_PAGED_TEMPLATE, "process")) + assertEquals("class org.springframework.data.domain.PageImpl", testCase.getFieldValue(RESULT_CLASS_FIELD_ID)) + + updateQuery("case: processIdentifier == '" + testProcess.identifier + "'") + assertThrows(IllegalArgumentException.class, () -> pressButton(String.format(SEARCH_PAGED_TEMPLATE, "process"))) + } + + @Test + void caseTest() { + updateQuery("case: processIdentifier == '" + testProcess.identifier + "'") + pressButton(String.format(SEARCH_ONE_TEMPLATE, "case")) + assertEquals("class com.netgrif.application.engine.workflow.domain.Case", testCase.getFieldValue(RESULT_CLASS_FIELD_ID)) + + updateQuery("cases: processIdentifier == '" + testProcess.identifier + "'") + pressButton(String.format(SEARCH_TEMPLATE, "case")) + assertEquals("class java.util.Collections\$UnmodifiableRandomAccessList", testCase.getFieldValue(RESULT_CLASS_FIELD_ID)) + + pressButton(String.format(SEARCH_PAGED_TEMPLATE, "case")) + assertEquals("class org.springframework.data.domain.PageImpl", testCase.getFieldValue(RESULT_CLASS_FIELD_ID)) + + updateQuery("process: identifier == '" + testProcess.identifier + "'") + assertThrows(IllegalArgumentException.class, () -> pressButton(String.format(SEARCH_PAGED_TEMPLATE, "case"))) + } + + @Test + void taskTest() { + updateQuery("task: caseId == '" + testCase.stringId + "'") + pressButton(String.format(SEARCH_ONE_TEMPLATE, "task")) + assertEquals("class com.netgrif.application.engine.workflow.domain.Task", testCase.getFieldValue(RESULT_CLASS_FIELD_ID)) + + updateQuery("tasks: caseId == '" + testCase.stringId + "'") + pressButton(String.format(SEARCH_TEMPLATE, "task")) + assertEquals("class java.util.Collections\$UnmodifiableRandomAccessList", testCase.getFieldValue(RESULT_CLASS_FIELD_ID)) + + pressButton(String.format(SEARCH_PAGED_TEMPLATE, "task")) + assertEquals("class org.springframework.data.domain.PageImpl", testCase.getFieldValue(RESULT_CLASS_FIELD_ID)) + + updateQuery("process: identifier == '" + testProcess.identifier + "'") + assertThrows(IllegalArgumentException.class, () -> pressButton(String.format(SEARCH_PAGED_TEMPLATE, "task"))) + } + + @Test + void userTest() { + updateQuery("user: email == '" + superCreator.superUser.email + "'") + pressButton(String.format(SEARCH_ONE_TEMPLATE, "user")) + assertEquals("class com.netgrif.application.engine.auth.domain.User", testCase.getFieldValue(RESULT_CLASS_FIELD_ID)) + + updateQuery("users: email == '" + superCreator.superUser.email + "'") + pressButton(String.format(SEARCH_TEMPLATE, "user")) + assertEquals("class java.util.Collections\$UnmodifiableRandomAccessList", testCase.getFieldValue(RESULT_CLASS_FIELD_ID)) + + pressButton(String.format(SEARCH_PAGED_TEMPLATE, "user")) + assertEquals("class org.springframework.data.domain.PageImpl", testCase.getFieldValue(RESULT_CLASS_FIELD_ID)) + + updateQuery("process: identifier == '" + testProcess.identifier + "'") + assertThrows(IllegalArgumentException.class, () -> pressButton(String.format(SEARCH_PAGED_TEMPLATE, "user"))) + } + + private void setData(Map> dataSet) { + testCase = dataService.setData(testCaseTaskId, ImportHelper.populateDataset(dataSet)).case + } + + private void pressButton(String buttonFieldId) { + setData([(buttonFieldId): ["value": 0, "type": FieldType.BUTTON.name]]) + } + + private void updateQuery(String query) { + setData([(QUERY_FIELD_ID): ["value": query, "type": FieldType.TEXT.name]]) + } +} diff --git a/src/test/groovy/com/netgrif/application/engine/pfql/QueryLangTest.java b/src/test/java/com/netgrif/application/engine/pfql/QueryLangTest.java similarity index 100% rename from src/test/groovy/com/netgrif/application/engine/pfql/QueryLangTest.java rename to src/test/java/com/netgrif/application/engine/pfql/QueryLangTest.java diff --git a/src/test/groovy/com/netgrif/application/engine/pfql/utils/MongoDbUtils.java b/src/test/java/com/netgrif/application/engine/pfql/utils/MongoDbUtils.java similarity index 100% rename from src/test/groovy/com/netgrif/application/engine/pfql/utils/MongoDbUtils.java rename to src/test/java/com/netgrif/application/engine/pfql/utils/MongoDbUtils.java diff --git a/src/test/groovy/com/netgrif/application/engine/pfql/utils/SearchTestUtils.java b/src/test/java/com/netgrif/application/engine/pfql/utils/SearchTestUtils.java similarity index 100% rename from src/test/groovy/com/netgrif/application/engine/pfql/utils/SearchTestUtils.java rename to src/test/java/com/netgrif/application/engine/pfql/utils/SearchTestUtils.java diff --git a/src/test/resources/petriNets/query_lang_test.xml b/src/test/resources/petriNets/query_lang_test.xml new file mode 100644 index 00000000000..248a34e829d --- /dev/null +++ b/src/test/resources/petriNets/query_lang_test.xml @@ -0,0 +1,246 @@ + + query_lang_test + QLT + query_lang_test + device_hub + true + false + false + + + input_query + + </data> + + <data type="text"> + <id>result_class</id> + <title/> + </data> + + <data type="text"> + <id>result</id> + <title/> + </data> + + <data type="button"> + <id>general_search</id> + <title/> + <event type="set"> + <actions phase="post"> + <action> + input_query: f.input_query, + result_class: f.result_class; + + change result_class value { search(input_query.value)?.class?.toString() } + </action> + </actions> + </event> + </data> + <data type="button"> + <id>general_count</id> + <title/> + <event type="set"> + <actions phase="post"> + <action> + input_query: f.input_query, + result: f.result; + + change result value { count(input_query.value) } + </action> + </actions> + </event> + </data> + <data type="button"> + <id>general_exists</id> + <title/> + <event type="set"> + <actions phase="post"> + <action> + input_query: f.input_query, + result: f.result; + + change result value { exists(input_query.value) } + </action> + </actions> + </event> + </data> + + <data type="button"> + <id>user_search_one</id> + <title/> + <event type="set"> + <actions phase="post"> + <action> + input_query: f.input_query, + result_class: f.result_class; + + change result_class value { searchUser(input_query.value)?.class?.toString() } + </action> + </actions> + </event> + </data> + <data type="button"> + <id>user_search</id> + <title/> + <event type="set"> + <actions phase="post"> + <action> + input_query: f.input_query, + result_class: f.result_class; + + change result_class value { searchUsers(input_query.value)?.class?.toString()} + </action> + </actions> + </event> + </data> + <data type="button"> + <id>user_paged_search</id> + <title/> + <event type="set"> + <actions phase="post"> + <action> + input_query: f.input_query, + result_class: f.result_class; + + change result_class value { pagedSearchUsers(input_query.value)?.class?.toString() } + </action> + </actions> + </event> + </data> + + <data type="button"> + <id>task_search_one</id> + <title/> + <event type="set"> + <actions phase="post"> + <action> + input_query: f.input_query, + result_class: f.result_class; + + change result_class value { searchTask(input_query.value)?.class?.toString() } + </action> + </actions> + </event> + </data> + <data type="button"> + <id>task_search</id> + <title/> + <event type="set"> + <actions phase="post"> + <action> + input_query: f.input_query, + result_class: f.result_class; + + change result_class value { searchTasks(input_query.value)?.class?.toString() } + </action> + </actions> + </event> + </data> + <data type="button"> + <id>task_paged_search</id> + <title/> + <event type="set"> + <actions phase="post"> + <action> + input_query: f.input_query, + result_class: f.result_class; + + change result_class value { pagedSearchTasks(input_query.value)?.class?.toString() } + </action> + </actions> + </event> + </data> + + <data type="button"> + <id>process_search_one</id> + <title/> + <event type="set"> + <actions phase="post"> + <action> + input_query: f.input_query, + result_class: f.result_class; + + change result_class value { searchProcess(input_query.value)?.class?.toString() } + </action> + </actions> + </event> + </data> + <data type="button"> + <id>process_search</id> + <title/> + <event type="set"> + <actions phase="post"> + <action> + input_query: f.input_query, + result_class: f.result_class; + + change result_class value { searchProcesses(input_query.value)?.class?.toString() } + </action> + </actions> + </event> + </data> + <data type="button"> + <id>process_paged_search</id> + <title/> + <event type="set"> + <actions phase="post"> + <action> + input_query: f.input_query, + result_class: f.result_class; + + change result_class value { pagedSearchProcesses(input_query.value)?.class?.toString() } + </action> + </actions> + </event> + </data> + + <data type="button"> + <id>case_search_one</id> + <title/> + <event type="set"> + <actions phase="post"> + <action> + input_query: f.input_query, + result_class: f.result_class; + + change result_class value { searchCase(input_query.value)?.class?.toString() } + </action> + </actions> + </event> + </data> + <data type="button"> + <id>case_search</id> + <title/> + <event type="set"> + <actions phase="post"> + <action> + input_query: f.input_query, + result_class: f.result_class; + + change result_class value { searchCases(input_query.value)?.class?.toString() } + </action> + </actions> + </event> + </data> + <data type="button"> + <id>case_paged_search</id> + <title/> + <event type="set"> + <actions phase="post"> + <action> + input_query: f.input_query, + result_class: f.result_class; + + change result_class value { pagedSearchCases(input_query.value)?.class?.toString() } + </action> + </actions> + </event> + </data> + + <transition> + <id>t1</id> + <x>700</x> + <y>180</y> + <label>TEST API</label> + </transition> +</document> \ No newline at end of file From 247cc243868a83ded611cf47318a3131a6e2b3b6 Mon Sep 17 00:00:00 2001 From: chvostek <chvostek@netgrif.com> Date: Mon, 8 Jun 2026 12:34:37 +0200 Subject: [PATCH 12/17] [NAE-2443] PFQL support - implement tests for search services - remove redundant default pageable from IResourceSearchService --- .../pfql/service/IResourceSearchService.java | 13 -- .../caseresource/CaseSearchService.java | 1 - .../processresource/ProcessSearchService.java | 1 - .../taskresource/TaskSearchService.java | 1 - .../userresource/UserSearchService.java | 77 ++++++++- .../engine/pfql/QueryLangActionTest.groovy | 2 - .../engine/pfql/CaseSearchServiceTest.java | 151 ++++++++++++++++++ .../engine/pfql/ProcessSearchServiceTest.java | 120 ++++++++++++++ .../engine/pfql/TaskSearchServiceTest.java | 119 ++++++++++++++ .../engine/pfql/UserSearchServiceTest.java | 96 +++++++++++ .../resources/petriNets/query_lang_test.xml | 2 +- 11 files changed, 562 insertions(+), 21 deletions(-) create mode 100644 src/test/java/com/netgrif/application/engine/pfql/CaseSearchServiceTest.java create mode 100644 src/test/java/com/netgrif/application/engine/pfql/ProcessSearchServiceTest.java create mode 100644 src/test/java/com/netgrif/application/engine/pfql/TaskSearchServiceTest.java create mode 100644 src/test/java/com/netgrif/application/engine/pfql/UserSearchServiceTest.java diff --git a/src/main/java/com/netgrif/application/engine/pfql/service/IResourceSearchService.java b/src/main/java/com/netgrif/application/engine/pfql/service/IResourceSearchService.java index 187bf7157d0..41aa7cc11e6 100644 --- a/src/main/java/com/netgrif/application/engine/pfql/service/IResourceSearchService.java +++ b/src/main/java/com/netgrif/application/engine/pfql/service/IResourceSearchService.java @@ -40,10 +40,6 @@ public interface IResourceSearchService<Resource> { boolean exists(String queryString); boolean exists(QueryLangEvaluator evaluator); - static Pageable getDefaultPageable() { - return PageRequest.ofSize(100); - } - // todo 2443 javadoc default void checkEvaluatorNotNull(QueryLangEvaluator evaluator) { if (evaluator == null) { @@ -51,15 +47,6 @@ default void checkEvaluatorNotNull(QueryLangEvaluator evaluator) { } } - // todo 2443 javadoc - default void updateWithDefaultPageableIfMissing(QueryLangEvaluator evaluator, Logger logger) { - if (evaluator.getPageable() == null) { - Pageable pageable = getDefaultPageable(); - logger.debug("Pageable was missing. Using default pageable: {}", pageable); - evaluator.setPageable(getDefaultPageable()); - } - } - // todo 2443 javadoc default void checkEvaluatorIsMultiple(QueryLangEvaluator evaluator) { if (!evaluator.getMultiple()) { diff --git a/src/main/java/com/netgrif/application/engine/pfql/service/caseresource/CaseSearchService.java b/src/main/java/com/netgrif/application/engine/pfql/service/caseresource/CaseSearchService.java index 49edc893e21..1a480043c6b 100644 --- a/src/main/java/com/netgrif/application/engine/pfql/service/caseresource/CaseSearchService.java +++ b/src/main/java/com/netgrif/application/engine/pfql/service/caseresource/CaseSearchService.java @@ -113,7 +113,6 @@ public Page<Case> searchAll(QueryLangEvaluator evaluator) { checkEvaluatorNotNull(evaluator); checkEvaluatorIsMultiple(evaluator); checkEvaluatorResourceType(evaluator); - updateWithDefaultPageableIfMissing(evaluator, log); log.debug("Searching for all cases using {} with pagination: page={}, size={}", evaluator.getSearchWithElastic() ? "Elasticsearch" : "MongoDB", diff --git a/src/main/java/com/netgrif/application/engine/pfql/service/processresource/ProcessSearchService.java b/src/main/java/com/netgrif/application/engine/pfql/service/processresource/ProcessSearchService.java index 3634b3dd3e3..4f945338032 100644 --- a/src/main/java/com/netgrif/application/engine/pfql/service/processresource/ProcessSearchService.java +++ b/src/main/java/com/netgrif/application/engine/pfql/service/processresource/ProcessSearchService.java @@ -110,7 +110,6 @@ public Page<PetriNet> searchAll(QueryLangEvaluator evaluator) { checkEvaluatorNotNull(evaluator); checkEvaluatorIsMultiple(evaluator); checkEvaluatorResourceType(evaluator); - updateWithDefaultPageableIfMissing(evaluator, log); // todo implement Elasticsearch search (service layer and evaluator layer) diff --git a/src/main/java/com/netgrif/application/engine/pfql/service/taskresource/TaskSearchService.java b/src/main/java/com/netgrif/application/engine/pfql/service/taskresource/TaskSearchService.java index 9981c11f6ae..d34fb17dfdc 100644 --- a/src/main/java/com/netgrif/application/engine/pfql/service/taskresource/TaskSearchService.java +++ b/src/main/java/com/netgrif/application/engine/pfql/service/taskresource/TaskSearchService.java @@ -122,7 +122,6 @@ public Page<Task> searchAll(QueryLangEvaluator evaluator) { checkEvaluatorNotNull(evaluator); checkEvaluatorIsMultiple(evaluator); checkEvaluatorResourceType(evaluator); - updateWithDefaultPageableIfMissing(evaluator, log); log.debug("Searching for all tasks using {}", evaluator.getSearchWithElastic() ? "Elasticsearch" : "MongoDB"); if (evaluator.getSearchWithElastic()) { diff --git a/src/main/java/com/netgrif/application/engine/pfql/service/userresource/UserSearchService.java b/src/main/java/com/netgrif/application/engine/pfql/service/userresource/UserSearchService.java index 4c6b65fa2c5..ba659c3d508 100644 --- a/src/main/java/com/netgrif/application/engine/pfql/service/userresource/UserSearchService.java +++ b/src/main/java/com/netgrif/application/engine/pfql/service/userresource/UserSearchService.java @@ -12,25 +12,56 @@ import static com.netgrif.application.engine.pfql.service.utils.SearchUtils.evaluateQuery; +/** + * Service for searching and querying user resources using PFQL (Process Flow Query Language). + * <p> + * This service provides methods to search for users, count users, and check user existence + * based on PFQL query strings or evaluated query objects. It delegates the actual MongoDB + * queries to the {@link IUserService}. + * </p> + * + * @see IResourceSearchService + * @see IUserService + * @see QueryLangEvaluator + */ @Slf4j @Service @RequiredArgsConstructor public class UserSearchService implements IResourceSearchService<IUser> { private final IUserService userService; - // todo 2443 javadoc + /** + * Returns the resource type handled by this search service. + * + * @return {@link QueryType#USER} indicating this service handles user resources + */ @Override public QueryType getQueryResourceType() { return QueryType.USER; } + /** + * Searches for a single user using a PFQL query string. + * + * @param queryString the PFQL query string to search with (e.g., "user: email == 'user@example.com'") + * @return the first user matching the query, or null if no user is found + * @throws IllegalArgumentException if the query string is invalid or evaluates to a non-USER resource type + */ @Override public IUser searchOne(String queryString) { log.debug("Searching for single user with query: {}", queryString); return searchOne(evaluateQuery(queryString)); } + /** + * Searches for a single user using a pre-evaluated query. + * + * @param evaluator the evaluated query object containing the MongoDB query and metadata + * @return the first user matching the query, or null if no user is found + * @throws IllegalArgumentException if the evaluator is null, not configured for single results, + * or has a resource type other than USER + */ @Override public IUser searchOne(QueryLangEvaluator evaluator) { checkEvaluatorNotNull(evaluator); @@ -44,18 +75,32 @@ public IUser searchOne(QueryLangEvaluator evaluator) { return result; } + /** + * Searches for all users matching a PFQL query string with pagination support. + * + * @param queryString the PFQL query string to search with (e.g., "users: email like '%@example.com'") + * @return a page of users matching the query + * @throws IllegalArgumentException if the query string is invalid or evaluates to a non-USER resource type + */ @Override public Page<IUser> searchAll(String queryString) { log.debug("Searching for all users with query: {}", queryString); return searchAll(evaluateQuery(queryString)); } + /** + * Searches for all users matching a pre-evaluated query with pagination support. + * + * @param evaluator the evaluated query object containing the MongoDB query, pagination settings, and metadata + * @return a page of users matching the query with pagination information + * @throws IllegalArgumentException if the evaluator is null, not configured for multiple results, + * or has a resource type other than USER + */ @Override public Page<IUser> searchAll(QueryLangEvaluator evaluator) { checkEvaluatorNotNull(evaluator); checkEvaluatorIsMultiple(evaluator); checkEvaluatorResourceType(evaluator); - updateWithDefaultPageableIfMissing(evaluator, log); log.debug("Searching for all users using MongoDB with pagination: page={}, size={}", evaluator.getPageable().getPageNumber(), evaluator.getPageable().getPageSize()); @@ -65,12 +110,26 @@ public Page<IUser> searchAll(QueryLangEvaluator evaluator) { return result; } + /** + * Counts the number of users matching a PFQL query string. + * + * @param queryString the PFQL query string to count with (e.g., "users: email like '%@example.com'") + * @return the number of users matching the query + * @throws IllegalArgumentException if the query string is invalid or evaluates to a non-USER resource type + */ @Override public long count(String queryString) { log.debug("Counting users with query: {}", queryString); return count(evaluateQuery(queryString)); } + /** + * Counts the number of users matching a pre-evaluated query. + * + * @param evaluator the evaluated query object containing the MongoDB query and metadata + * @return the number of users matching the query + * @throws IllegalArgumentException if the evaluator is null or has a resource type other than USER + */ @Override public long count(QueryLangEvaluator evaluator) { checkEvaluatorNotNull(evaluator); @@ -83,12 +142,26 @@ public long count(QueryLangEvaluator evaluator) { return result; } + /** + * Checks if any user exists that matches a PFQL query string. + * + * @param queryString the PFQL query string to check with (e.g., "user: email == 'user@example.com'") + * @return true if at least one user matching the query exists, false otherwise + * @throws IllegalArgumentException if the query string is invalid or evaluates to a non-USER resource type + */ @Override public boolean exists(String queryString) { log.debug("Checking existence of user with query: {}", queryString); return exists(evaluateQuery(queryString)); } + /** + * Checks if any user exists that matches a pre-evaluated query. + * + * @param evaluator the evaluated query object containing the MongoDB query and metadata + * @return true if at least one user matching the query exists, false otherwise + * @throws IllegalArgumentException if the evaluator is null or has a resource type other than USER + */ @Override public boolean exists(QueryLangEvaluator evaluator) { checkEvaluatorNotNull(evaluator); diff --git a/src/test/groovy/com/netgrif/application/engine/pfql/QueryLangActionTest.groovy b/src/test/groovy/com/netgrif/application/engine/pfql/QueryLangActionTest.groovy index b5246cca2ec..944266e13e6 100644 --- a/src/test/groovy/com/netgrif/application/engine/pfql/QueryLangActionTest.groovy +++ b/src/test/groovy/com/netgrif/application/engine/pfql/QueryLangActionTest.groovy @@ -7,7 +7,6 @@ import com.netgrif.application.engine.startup.ImportHelper import com.netgrif.application.engine.startup.SuperCreator import com.netgrif.application.engine.workflow.domain.Case import com.netgrif.application.engine.workflow.service.interfaces.IDataService -import lombok.extern.slf4j.Slf4j import org.junit.jupiter.api.BeforeEach import org.junit.jupiter.api.Test import org.junit.jupiter.api.extension.ExtendWith @@ -19,7 +18,6 @@ import org.springframework.test.context.junit.jupiter.SpringExtension import static org.junit.jupiter.api.Assertions.assertEquals import static org.junit.jupiter.api.Assertions.assertThrows -@Slf4j @SpringBootTest @ActiveProfiles(["test"]) @ExtendWith(SpringExtension.class) diff --git a/src/test/java/com/netgrif/application/engine/pfql/CaseSearchServiceTest.java b/src/test/java/com/netgrif/application/engine/pfql/CaseSearchServiceTest.java new file mode 100644 index 00000000000..15c4d570f62 --- /dev/null +++ b/src/test/java/com/netgrif/application/engine/pfql/CaseSearchServiceTest.java @@ -0,0 +1,151 @@ +package com.netgrif.application.engine.pfql; + +import com.netgrif.application.engine.TestHelper; +import com.netgrif.application.engine.petrinet.domain.PetriNet; +import com.netgrif.application.engine.petrinet.domain.dataset.FieldType; +import com.netgrif.application.engine.pfql.domain.enums.QueryType; +import com.netgrif.application.engine.pfql.service.caseresource.CaseSearchService; +import com.netgrif.application.engine.startup.ImportHelper; +import com.netgrif.application.engine.workflow.domain.Case; +import com.netgrif.application.engine.workflow.domain.TaskPair; +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.data.domain.Page; +import org.springframework.test.context.ActiveProfiles; +import org.springframework.test.context.junit.jupiter.SpringExtension; + +import java.util.Map; +import java.util.Optional; + +import static org.junit.jupiter.api.Assertions.*; + +@SpringBootTest +@ActiveProfiles({"test"}) +@ExtendWith(SpringExtension.class) +public class CaseSearchServiceTest { + + private static final String QUERY_FIELD_ID = "input_query"; + + @Autowired + private TestHelper testHelper; + + @Autowired + private CaseSearchService caseSearchService; + + @Autowired + private ImportHelper importHelper; + + private Case testCase; + + @BeforeEach + protected void beforeEach() { + testHelper.truncateDbs(); + Optional<PetriNet> createdNetOpt = importHelper.createNet("/query_lang_test.xml"); + assertTrue(createdNetOpt.isPresent()); + testCase = importHelper.createCase("test", createdNetOpt.get()); + } + + @Test + public void queryResourceTypeTest() { + assertEquals(QueryType.CASE, caseSearchService.getQueryResourceType()); + } + + @Test + public void searchOneTest() throws InterruptedException { + assertThrows(IllegalArgumentException.class, () -> caseSearchService.searchOne((String) null)); + assertThrows(IllegalArgumentException.class, () -> caseSearchService.searchOne("cases: title eq 'test'")); + assertThrows(IllegalArgumentException.class, () -> caseSearchService.searchOne("process: identifier eq 'query_lang_test'")); + + Case result = caseSearchService.searchOne("case: title eq 'test'"); + assertNotNull(result); + assertNotNull(result.getPetriNet()); + assertEquals(testCase.getStringId(), result.getStringId()); + + setData(Map.of(QUERY_FIELD_ID, Map.of("value", "xxx", "type", FieldType.TEXT.getName()))); + Thread.sleep(2000); + result = caseSearchService.searchOne("case: data." + QUERY_FIELD_ID + ".value eq 'xxx'"); + assertNotNull(result); + assertNotNull(result.getPetriNet()); + assertEquals(testCase.getStringId(), result.getStringId()); + + result = caseSearchService.searchOne("case: title eq 'wrong'"); + assertNull(result); + } + + @Test + public void searchAllTest() throws InterruptedException { + assertThrows(IllegalArgumentException.class, () -> caseSearchService.searchAll((String) null)); + assertThrows(IllegalArgumentException.class, () -> caseSearchService.searchAll("case: title eq 'test'")); + assertThrows(IllegalArgumentException.class, () -> caseSearchService.searchAll("processes: identifier eq 'query_lang_test'")); + + Page<Case> result = caseSearchService.searchAll("cases: title eq 'test'"); + assertNotNull(result); + assertEquals(1, result.getTotalElements()); + assertNotNull(result.getContent().get(0).getPetriNet()); + assertEquals(testCase.getStringId(), result.getContent().get(0).getStringId()); + + setData(Map.of(QUERY_FIELD_ID, Map.of("value", "xxx", "type", FieldType.TEXT.getName()))); + Thread.sleep(2000); + result = caseSearchService.searchAll("cases: data." + QUERY_FIELD_ID + ".value eq 'xxx'"); + assertNotNull(result); + assertEquals(1, result.getTotalElements()); + assertNotNull(result.getContent().get(0).getPetriNet()); + assertEquals(testCase.getStringId(), result.getContent().get(0).getStringId()); + + result = caseSearchService.searchAll("cases: title eq 'wrong'"); + assertNotNull(result); + assertEquals(0, result.getTotalElements()); + } + + @Test + public void countTest() throws InterruptedException { + assertThrows(IllegalArgumentException.class, () -> caseSearchService.count((String) null)); + assertThrows(IllegalArgumentException.class, () -> caseSearchService.count("process: identifier eq 'query_lang_test'")); + + long result = caseSearchService.count("case: title eq 'test'"); + assertEquals(1, result); + + result = caseSearchService.count("cases: title eq 'test'"); + assertEquals(1, result); + + setData(Map.of(QUERY_FIELD_ID, Map.of("value", "xxx", "type", FieldType.TEXT.getName()))); + Thread.sleep(2000); + result = caseSearchService.count("cases: data." + QUERY_FIELD_ID + ".value eq 'xxx'"); + assertEquals(1, result); + + result = caseSearchService.count("cases: title eq 'wrong'"); + assertEquals(0, result); + } + + @Test + public void existsTest() throws InterruptedException { + assertThrows(IllegalArgumentException.class, () -> caseSearchService.exists((String) null)); + assertThrows(IllegalArgumentException.class, () -> caseSearchService.exists("process: identifier eq 'query_lang_test'")); + + boolean result = caseSearchService.exists("case: title eq 'test'"); + assertTrue(result); + + result = caseSearchService.exists("cases: title eq 'test'"); + assertTrue(result); + + setData(Map.of(QUERY_FIELD_ID, Map.of("value", "xxx", "type", FieldType.TEXT.getName()))); + Thread.sleep(2000); + result = caseSearchService.exists("cases: data." + QUERY_FIELD_ID + ".value eq 'xxx'"); + assertTrue(result); + + result = caseSearchService.exists("cases: title eq 'wrong'"); + assertFalse(result); + } + + private void setData(Map<String, Map<String, String>> dataSet) { + String taskId = testCase.getTasks().stream() + .filter(taskPair -> taskPair.getTransition().equals("t1")) + .map(TaskPair::getTask) + .findFirst().get(); + testCase = importHelper.setTaskData(taskId, dataSet).getCase(); + } + +} diff --git a/src/test/java/com/netgrif/application/engine/pfql/ProcessSearchServiceTest.java b/src/test/java/com/netgrif/application/engine/pfql/ProcessSearchServiceTest.java new file mode 100644 index 00000000000..44fefe6f187 --- /dev/null +++ b/src/test/java/com/netgrif/application/engine/pfql/ProcessSearchServiceTest.java @@ -0,0 +1,120 @@ +package com.netgrif.application.engine.pfql; + +import com.netgrif.application.engine.TestHelper; +import com.netgrif.application.engine.petrinet.domain.PetriNet; +import com.netgrif.application.engine.pfql.domain.enums.QueryType; +import com.netgrif.application.engine.pfql.service.processresource.ProcessSearchService; +import com.netgrif.application.engine.startup.ImportHelper; +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.data.domain.Page; +import org.springframework.test.context.ActiveProfiles; +import org.springframework.test.context.junit.jupiter.SpringExtension; + +import java.util.Optional; + +import static org.junit.jupiter.api.Assertions.*; + +@SpringBootTest +@ActiveProfiles({"test"}) +@ExtendWith(SpringExtension.class) +public class ProcessSearchServiceTest { + + @Autowired + private TestHelper testHelper; + + @Autowired + private ProcessSearchService processSearchService; + + @Autowired + private ImportHelper importHelper; + + private PetriNet testNet; + + @BeforeEach + protected void beforeEach() { + testHelper.truncateDbs(); + Optional<PetriNet> createdNetOpt = importHelper.createNet("/query_lang_test.xml"); + assertTrue(createdNetOpt.isPresent()); + testNet = createdNetOpt.get(); + } + + @Test + public void queryResourceTypeTest() { + assertEquals(QueryType.PROCESS, processSearchService.getQueryResourceType()); + } + + @Test + public void searchOneTest() { + assertThrows(IllegalArgumentException.class, () -> processSearchService.searchOne((String) null)); + assertThrows(IllegalArgumentException.class, () -> processSearchService.searchOne("processes: identifier eq 'xxx'")); + assertThrows(IllegalArgumentException.class, () -> processSearchService.searchOne("case: processIdentifier eq 'xxx'")); + + PetriNet result = processSearchService.searchOne("process: identifier eq 'query_lang_test'"); + assertNotNull(result); + assertEquals(testNet.getStringId(), result.getStringId()); + + result = processSearchService.searchOne("process: identifier eq 'wrong'"); + assertNull(result); + } + + @Test + public void searchAllTest() { + assertThrows(IllegalArgumentException.class, () -> processSearchService.searchAll((String) null)); + assertThrows(IllegalArgumentException.class, () -> processSearchService.searchAll("process: identifier eq 'xxx'")); + assertThrows(IllegalArgumentException.class, () -> processSearchService.searchAll("cases: processIdentifier eq 'xxx'")); + + Page<PetriNet> result = processSearchService.searchAll("processes: identifier eq 'query_lang_test'"); + assertNotNull(result); + assertEquals(1, result.getTotalElements()); + assertEquals(20, result.getPageable().getPageSize()); + assertEquals(testNet.getStringId(), result.getContent().get(0).getStringId()); + + result = processSearchService.searchAll("processes: identifier eq 'query_lang_test' page 0 size 67"); + assertNotNull(result); + assertEquals(67, result.getPageable().getPageSize()); + + result = processSearchService.searchAll("processes: identifier eq 'wrong'"); + assertNotNull(result); + assertEquals(0, result.getTotalElements()); + } + + @Test + public void countTest() { + assertThrows(IllegalArgumentException.class, () -> processSearchService.count((String) null)); + assertThrows(IllegalArgumentException.class, () -> processSearchService.count("case: identifier eq 'xxx'")); + + long result = processSearchService.count("process: identifier eq 'query_lang_test'"); + assertEquals(1, result); + + result = processSearchService.count("processes: identifier eq 'query_lang_test'"); + assertEquals(1, result); + + result = processSearchService.count("processes: identifier eq 'query_lang_test'"); + assertEquals(1, result); + + result = processSearchService.count("processes: identifier eq 'wrong'"); + assertEquals(0, result); + } + + @Test + public void existsTest() { + assertThrows(IllegalArgumentException.class, () -> processSearchService.exists((String) null)); + assertThrows(IllegalArgumentException.class, () -> processSearchService.exists("case: identifier eq 'xxx'")); + + boolean result = processSearchService.exists("process: identifier eq 'query_lang_test'"); + assertTrue(result); + + result = processSearchService.exists("processes: identifier eq 'query_lang_test'"); + assertTrue(result); + + result = processSearchService.exists("processes: identifier eq 'query_lang_test'"); + assertTrue(result); + + result = processSearchService.exists("processes: identifier eq 'wrong'"); + assertFalse(result); + } +} diff --git a/src/test/java/com/netgrif/application/engine/pfql/TaskSearchServiceTest.java b/src/test/java/com/netgrif/application/engine/pfql/TaskSearchServiceTest.java new file mode 100644 index 00000000000..8d8885e2231 --- /dev/null +++ b/src/test/java/com/netgrif/application/engine/pfql/TaskSearchServiceTest.java @@ -0,0 +1,119 @@ +package com.netgrif.application.engine.pfql; + +import com.netgrif.application.engine.TestHelper; +import com.netgrif.application.engine.petrinet.domain.PetriNet; +import com.netgrif.application.engine.pfql.domain.enums.QueryType; +import com.netgrif.application.engine.pfql.service.taskresource.TaskSearchService; +import com.netgrif.application.engine.startup.ImportHelper; +import com.netgrif.application.engine.workflow.domain.Case; +import com.netgrif.application.engine.workflow.domain.Task; +import com.netgrif.application.engine.workflow.domain.TaskPair; +import org.bson.types.ObjectId; +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.data.domain.Page; +import org.springframework.test.context.ActiveProfiles; +import org.springframework.test.context.junit.jupiter.SpringExtension; + +import java.util.Optional; + +import static org.junit.jupiter.api.Assertions.*; +import static org.junit.jupiter.api.Assertions.assertThrows; + +@SpringBootTest +@ActiveProfiles({"test"}) +@ExtendWith(SpringExtension.class) +public class TaskSearchServiceTest { + + @Autowired + private TestHelper testHelper; + + @Autowired + private TaskSearchService taskSearchService; + + @Autowired + private ImportHelper importHelper; + + private String testTaskId; + + @BeforeEach + protected void beforeEach() { + testHelper.truncateDbs(); + Optional<PetriNet> createdNetOpt = importHelper.createNet("/query_lang_test.xml"); + assertTrue(createdNetOpt.isPresent()); + Case testCase = importHelper.createCase("test", createdNetOpt.get()); + testTaskId = testCase.getTasks().stream() + .filter(taskPair -> taskPair.getTransition().equals("t1")) + .map(TaskPair::getTask) + .findFirst().get(); + } + + @Test + public void queryResourceTypeTest() { + assertEquals(QueryType.TASK, taskSearchService.getQueryResourceType()); + } + + @Test + public void searchOneTest() { + assertThrows(IllegalArgumentException.class, () -> taskSearchService.searchOne((String) null)); + assertThrows(IllegalArgumentException.class, () -> taskSearchService.searchOne("tasks: title eq 'test'")); + assertThrows(IllegalArgumentException.class, () -> taskSearchService.searchOne("process: identifier eq 'query_lang_test'")); + + Task result = taskSearchService.searchOne("task: id eq '" + testTaskId + "'"); + assertNotNull(result); + assertEquals(testTaskId, result.getStringId()); + + result = taskSearchService.searchOne("task: id eq '" + new ObjectId() + "'"); + assertNull(result); + } + + @Test + public void searchAllTest() { + assertThrows(IllegalArgumentException.class, () -> taskSearchService.searchAll((String) null)); + assertThrows(IllegalArgumentException.class, () -> taskSearchService.searchAll("task: title eq 'test'")); + assertThrows(IllegalArgumentException.class, () -> taskSearchService.searchAll("processes: identifier eq 'query_lang_test'")); + + Page<Task> result = taskSearchService.searchAll("tasks: id eq '" + testTaskId + "'"); + assertNotNull(result); + assertEquals(1, result.getTotalElements()); + assertEquals(testTaskId, result.getContent().get(0).getStringId()); + + result = taskSearchService.searchAll("tasks: id eq '" + new ObjectId() + "'"); + assertNotNull(result); + assertEquals(0, result.getTotalElements()); + } + + @Test + public void countTest() { + assertThrows(IllegalArgumentException.class, () -> taskSearchService.count((String) null)); + assertThrows(IllegalArgumentException.class, () -> taskSearchService.count("process: identifier eq 'query_lang_test'")); + + long result = taskSearchService.count("task: id eq '" + testTaskId + "'"); + assertEquals(1, result); + + result = taskSearchService.count("tasks: id eq '" + testTaskId + "'"); + assertEquals(1, result); + + result = taskSearchService.count("tasks: id eq '" + new ObjectId() + "'"); + assertEquals(0, result); + } + + @Test + public void existsTest() { + assertThrows(IllegalArgumentException.class, () -> taskSearchService.exists((String) null)); + assertThrows(IllegalArgumentException.class, () -> taskSearchService.exists("process: identifier eq 'query_lang_test'")); + + boolean result = taskSearchService.exists("task: id eq '" + testTaskId + "'"); + assertTrue(result); + + result = taskSearchService.exists("tasks: id eq '" + testTaskId + "'"); + assertTrue(result); + + result = taskSearchService.exists("tasks: id eq '" + new ObjectId() + "'"); + assertFalse(result); + } + +} diff --git a/src/test/java/com/netgrif/application/engine/pfql/UserSearchServiceTest.java b/src/test/java/com/netgrif/application/engine/pfql/UserSearchServiceTest.java new file mode 100644 index 00000000000..4f3c0a9ec41 --- /dev/null +++ b/src/test/java/com/netgrif/application/engine/pfql/UserSearchServiceTest.java @@ -0,0 +1,96 @@ +package com.netgrif.application.engine.pfql; + +import com.netgrif.application.engine.TestHelper; +import com.netgrif.application.engine.auth.domain.IUser; +import com.netgrif.application.engine.pfql.domain.enums.QueryType; +import com.netgrif.application.engine.pfql.service.userresource.UserSearchService; +import com.netgrif.application.engine.startup.SuperCreator; +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.data.domain.Page; +import org.springframework.test.context.ActiveProfiles; +import org.springframework.test.context.junit.jupiter.SpringExtension; + +import static org.junit.jupiter.api.Assertions.*; + +@SpringBootTest +@ActiveProfiles({"test"}) +@ExtendWith(SpringExtension.class) +public class UserSearchServiceTest { + + @Autowired + private TestHelper testHelper; + + @Autowired + private UserSearchService userSearchService; + + @Autowired + private SuperCreator superCreator; + + @BeforeEach + protected void beforeEach() { + testHelper.truncateDbs(); + } + + @Test + public void queryResourceTypeTest() { + assertEquals(QueryType.USER, userSearchService.getQueryResourceType()); + } + + @Test + public void searchOneTest() { + assertThrows(IllegalArgumentException.class, () -> userSearchService.searchOne((String) null)); + assertThrows(IllegalArgumentException.class, () -> userSearchService.searchOne("users: title eq 'test'")); + assertThrows(IllegalArgumentException.class, () -> userSearchService.searchOne("process: identifier eq 'query_lang_test'")); + + IUser result = userSearchService.searchOne("user: email eq '" + superCreator.getSuperUser().getEmail() + "'"); + assertNotNull(result); + assertEquals(superCreator.getSuperUser().getStringId(), result.getStringId()); + + result = userSearchService.searchOne("user: email eq 'wrong'"); + assertNull(result); + } + + @Test + public void searchAllTest() { + assertThrows(IllegalArgumentException.class, () -> userSearchService.searchAll((String) null)); + assertThrows(IllegalArgumentException.class, () -> userSearchService.searchAll("user: title eq 'test'")); + assertThrows(IllegalArgumentException.class, () -> userSearchService.searchAll("processes: identifier eq 'query_lang_test'")); + + Page<IUser> result = userSearchService.searchAll("users: email eq '" + superCreator.getSuperUser().getEmail() + "'"); + assertNotNull(result); + assertEquals(1, result.getTotalElements()); + assertEquals(superCreator.getSuperUser().getStringId(), result.getContent().get(0).getStringId()); + + result = userSearchService.searchAll("users: email eq 'wrong'"); + assertNotNull(result); + assertEquals(0, result.getTotalElements()); + } + + @Test + public void countTest() { + assertThrows(IllegalArgumentException.class, () -> userSearchService.count((String) null)); + assertThrows(IllegalArgumentException.class, () -> userSearchService.count("process: identifier eq 'query_lang_test'")); + + long result = userSearchService.count("users: email eq '" + superCreator.getSuperUser().getEmail() + "'"); + assertEquals(1, result); + + result = userSearchService.count("users: email eq 'wrong'"); + assertEquals(0, result); + } + + @Test + public void existsTest() { + assertThrows(IllegalArgumentException.class, () -> userSearchService.exists((String) null)); + assertThrows(IllegalArgumentException.class, () -> userSearchService.exists("process: identifier eq 'query_lang_test'")); + + boolean result = userSearchService.exists("users: email eq '" + superCreator.getSuperUser().getEmail() + "'"); + assertTrue(result); + + result = userSearchService.exists("users: email eq 'wrong'"); + assertFalse(result); + } +} diff --git a/src/test/resources/petriNets/query_lang_test.xml b/src/test/resources/petriNets/query_lang_test.xml index 248a34e829d..fcbec702dae 100644 --- a/src/test/resources/petriNets/query_lang_test.xml +++ b/src/test/resources/petriNets/query_lang_test.xml @@ -7,7 +7,7 @@ <anonymousRole>false</anonymousRole> <transitionRole>false</transitionRole> - <data type="text"> + <data type="text" immediate="true"> <id>input_query</id> <title/> </data> From d481707add3bc8e38c6c8ca98db20479f60d92da Mon Sep 17 00:00:00 2001 From: chvostek <chvostek@netgrif.com> Date: Mon, 8 Jun 2026 13:16:52 +0200 Subject: [PATCH 13/17] [NAE-2443] PFQL support - add javadoc for ActionDelegate search methods - improve lowcode experience for action delegate dedicated search methods - add javadoc for IResourceSearchService default methods - improve QueryLangActionTest --- .../logic/action/ActionDelegate.groovy | 380 +++++++++++++++++- .../pfql/service/IResourceSearchService.java | 31 +- .../caseresource/CaseSearchService.java | 3 + .../processresource/ProcessSearchService.java | 3 + .../taskresource/TaskSearchService.java | 3 + .../userresource/UserSearchService.java | 3 + .../pfql/service/utils/SearchUtils.java | 53 +++ .../engine/pfql/QueryLangActionTest.groovy | 32 ++ 8 files changed, 499 insertions(+), 9 deletions(-) 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 6bba9e60dfa..c3200f476d8 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 @@ -53,6 +53,7 @@ import com.netgrif.application.engine.pfql.service.caseresource.CaseSearchServic import com.netgrif.application.engine.pfql.service.processresource.ProcessSearchService import com.netgrif.application.engine.pfql.service.taskresource.TaskSearchService import com.netgrif.application.engine.pfql.service.userresource.UserSearchService +import com.netgrif.application.engine.pfql.service.utils.SearchUtils import com.netgrif.application.engine.rules.domain.RuleRepository import com.netgrif.application.engine.startup.DefaultFiltersRunner import com.netgrif.application.engine.startup.FilterRunner @@ -2694,88 +2695,433 @@ class ActionDelegate { return taskCase.getPetriNet().getDataSet().get(fieldId) } - // todo 2443 javadoc with examples for search methods - + /** + * Searches for a single {@link Case} matching the given query. + * <p> + * The query must start with the resource keyword {@code case} (singular). + * </p> + * Example: + * <pre> + * searchCase("case: processIdentifier eq 'query_test' and data.number_0.value == 3") + * searchCase("case: id eq '5f9b1c2d3e4f5a6b7c8d9e0f'") + * searchCase("id eq '5f9b1c2d3e4f5a6b7c8d9e0f'") + * </pre> + * + * @param query query language string starting with {@code case:} + * @return matching {@link Case} or {@code null} if none is found + */ Case searchCase(String query) { + query = SearchUtils.ensureStartsWithCase(query) return caseSearchService.searchOne(query) } + /** + * Searches for all {@link Case} instances matching the given query and returns a paged result. + * <p> + * The query must start with the resource keyword {@code cases} (plural) and may contain + * paging and sorting clauses. + * </p> + * Example: + * <pre> + * pagedSearchCases("cases: processIdentifier eq 'query_test' page 1 size 5 sort by title desc") + * pagedSearchCases("cases: author eq 'user@mail.com' and creationDate gt 2020-03-03") + * pagedSearchCases("author eq 'user@mail.com' and creationDate gt 2020-03-03") + * </pre> + * + * @param query query language string starting with {@code cases:} + * @return {@link Page} of matching cases + */ Page<Case> pagedSearchCases(String query) { + query = SearchUtils.ensureStartsWithCases(query) return caseSearchService.searchAll(query) } + /** + * Searches for all {@link Case} instances matching the given query and returns them as a list. + * <p> + * The query must start with the resource keyword {@code cases} (plural). This is a convenience + * method returning only the content of {@link #pagedSearchCases(String)}. + * </p> + * Example: + * <pre> + * searchCases("cases: processIdentifier eq 'query_test' and data.boolean_0.value == true") + * searchCases("cases: title contains 'Test' sort by creationDate desc") + * searchCases("title contains 'Test' sort by creationDate desc") + * </pre> + * + * @param query query language string starting with {@code cases:} + * @return list of matching cases + */ List<Case> searchCases(String query) { + query = SearchUtils.ensureStartsWithCases(query) return pagedSearchCases(query).content } + /** + * Counts the number of {@link Case} instances matching the given query. + * <p> + * The query must start with the resource keyword {@code cases} (plural). + * </p> + * Example: + * <pre> + * countCases("cases: processIdentifier eq 'query_test'") + * countCases("cases: data.boolean_0.value == true and data.text_0.value != '4'") + * countCases("data.boolean_0.value == true and data.text_0.value != '4'") + * </pre> + * + * @param query query language string starting with {@code cases:} + * @return number of matching cases + */ long countCases(String query) { + query = SearchUtils.ensureStartsWithCases(query) return caseSearchService.count(query) } + /** + * Checks whether at least one {@link Case} matching the given query exists. + * <p> + * The query must start with the resource keyword {@code cases} (plural). + * </p> + * Example: + * <pre> + * existsCase("cases: processIdentifier eq 'query_test'") + * existsCase("cases: id in ('5f9b1c2d3e4f5a6b7c8d9e0f', '5f9b1c2d3e4f5a6b7c8d9e10')") + * existsCase("id in ('5f9b1c2d3e4f5a6b7c8d9e0f', '5f9b1c2d3e4f5a6b7c8d9e10')") + * </pre> + * + * @param query query language string starting with {@code cases:} + * @return {@code true} if a matching case exists, {@code false} otherwise + */ boolean existsCase(String query) { + query = SearchUtils.ensureStartsWithCases(query) return caseSearchService.exists(query) } + /** + * Searches for a single {@link Task} matching the given query. + * <p> + * The query must start with the resource keyword {@code task} (singular). + * </p> + * Example: + * <pre> + * searchTask("task: transitionId eq 't1' and caseId eq '5f9b1c2d3e4f5a6b7c8d9e0f'") + * searchTask("task: id eq '5f9b1c2d3e4f5a6b7c8d9e0f'") + * searchTask("id eq '5f9b1c2d3e4f5a6b7c8d9e0f'") + * </pre> + * + * @param query query language string starting with {@code task:} + * @return matching {@link Task} or {@code null} if none is found + */ Task searchTask(String query) { + query = SearchUtils.ensureStartsWithTask(query) return taskSearchService.searchOne(query) } + /** + * Searches for all {@link Task} instances matching the given query and returns a paged result. + * <p> + * The query must start with the resource keyword {@code tasks} (plural) and may contain + * paging and sorting clauses. + * </p> + * Example: + * <pre> + * pagedSearchTasks("tasks: title eq 'test' page 0 size 10 sort by lastFinish desc") + * pagedSearchTasks("tasks: userId eq 'user1' and state eq enabled") + * pagedSearchTasks("userId eq 'user1' and state eq enabled") + * </pre> + * + * @param query query language string starting with {@code tasks:} + * @return {@link Page} of matching tasks + */ Page<Task> pagedSearchTasks(String query) { + query = SearchUtils.ensureStartsWithTasks(query) return taskSearchService.searchAll(query) } + /** + * Searches for all {@link Task} instances matching the given query and returns them as a list. + * <p> + * The query must start with the resource keyword {@code tasks} (plural). This is a convenience + * method returning only the content of {@link #pagedSearchTasks(String)}. + * </p> + * Example: + * <pre> + * searchTasks("tasks: processId eq 'my_process' and userId in ('user1', 'user2')") + * searchTasks("tasks: title contains 'Approve' sort by title asc") + * searchTasks("title contains 'Approve' sort by title asc") + * </pre> + * + * @param query query language string starting with {@code tasks:} + * @return list of matching tasks + */ List<Task> searchTasks(String query) { + query = SearchUtils.ensureStartsWithTasks(query) return pagedSearchTasks(query).content } + /** + * Counts the number of {@link Task} instances matching the given query. + * <p> + * The query must start with the resource keyword {@code tasks} (plural). + * </p> + * Example: + * <pre> + * countTasks("tasks: caseId eq '5f9b1c2d3e4f5a6b7c8d9e0f'") + * countTasks("tasks: transitionId eq 't1' and userId eq 'user1'") + * countTasks("transitionId eq 't1' and userId eq 'user1'") + * </pre> + * + * @param query query language string starting with {@code tasks:} + * @return number of matching tasks + */ long countTasks(String query) { + query = SearchUtils.ensureStartsWithTasks(query) return taskSearchService.count(query) } + /** + * Checks whether at least one {@link Task} matching the given query exists. + * <p> + * The query must start with the resource keyword {@code tasks} (plural). + * </p> + * Example: + * <pre> + * existsTask("tasks: caseId eq '5f9b1c2d3e4f5a6b7c8d9e0f'") + * existsTask("tasks: transitionId eq 't1' and userId not eq 'user1'") + * existsTask("transitionId eq 't1' and userId not eq 'user1'") + * </pre> + * + * @param query query language string starting with {@code tasks:} + * @return {@code true} if a matching task exists, {@code false} otherwise + */ boolean existsTask(String query) { + query = SearchUtils.ensureStartsWithTasks(query) return taskSearchService.exists(query) } + /** + * Searches for a single {@link PetriNet} (process) matching the given query. + * <p> + * The query must start with the resource keyword {@code process} (singular). + * </p> + * Example: + * <pre> + * searchProcess("process: identifier == 'query_test'") + * searchProcess("process: identifier eq 'my_process' and version eq 1.0.0") + * searchProcess("identifier eq 'my_process' and version eq 1.0.0") + * </pre> + * + * @param query query language string starting with {@code process:} + * @return matching {@link PetriNet} or {@code null} if none is found + */ PetriNet searchProcess(String query) { + query = SearchUtils.ensureStartsWithProcess(query) return processSearchService.searchOne(query) } + /** + * Searches for all {@link PetriNet} (process) instances matching the given query and returns a paged result. + * <p> + * The query must start with the resource keyword {@code processes} (plural) and may contain + * paging and sorting clauses. + * </p> + * Example: + * <pre> + * pagedSearchProcesses("processes: identifier eq 'my_process' page 0 size 10 sort by version desc") + * pagedSearchProcesses("processes: version in (1.0.0 : 2.0.0)") + * pagedSearchProcesses("version in (1.0.0 : 2.0.0)") + * </pre> + * + * @param query query language string starting with {@code processes:} + * @return {@link Page} of matching processes + */ Page<PetriNet> pagedSearchProcesses(String query) { + query = SearchUtils.ensureStartsWithProcesses(query) return processSearchService.searchAll(query) } + /** + * Searches for all {@link PetriNet} (process) instances matching the given query and returns them as a list. + * <p> + * The query must start with the resource keyword {@code processes} (plural). This is a convenience + * method returning only the content of {@link #pagedSearchProcesses(String)}. + * </p> + * Example: + * <pre> + * searchProcesses("processes: title contains 'Test' sort by identifier asc") + * searchProcesses("processes: identifier in ('process_a', 'process_b')") + * searchProcesses("identifier in ('process_a', 'process_b')") + * </pre> + * + * @param query query language string starting with {@code processes:} + * @return list of matching processes + */ List<PetriNet> searchProcesses(String query) { + query = SearchUtils.ensureStartsWithProcesses(query) return pagedSearchProcesses(query).content } + /** + * Counts the number of {@link PetriNet} (process) instances matching the given query. + * <p> + * The query must start with the resource keyword {@code processes} (plural). + * </p> + * Example: + * <pre> + * countProcesses("processes: identifier eq 'my_process'") + * countProcesses("processes: version gte 1.0.0") + * countProcesses("version gte 1.0.0") + * </pre> + * + * @param query query language string starting with {@code processes:} + * @return number of matching processes + */ long countProcesses(String query) { + query = SearchUtils.ensureStartsWithProcesses(query) return processSearchService.count(query) } + /** + * Checks whether at least one {@link PetriNet} (process) matching the given query exists. + * <p> + * The query must start with the resource keyword {@code processes} (plural). + * </p> + * Example: + * <pre> + * existsProcess("processes: identifier eq 'my_process'") + * existsProcess("processes: version eq 1.0.0") + * existsProcess("version eq 1.0.0") + * </pre> + * + * @param query query language string starting with {@code processes:} + * @return {@code true} if a matching process exists, {@code false} otherwise + */ boolean existsProcess(String query) { + query = SearchUtils.ensureStartsWithProcesses(query) return processSearchService.exists(query) } + /** + * Searches for a single {@link IUser} matching the given query. + * <p> + * The query must start with the resource keyword {@code user} (singular). + * </p> + * Example: + * <pre> + * searchUser("user: email eq 'user@mail.com'") + * searchUser("user: name eq 'John' and surname eq 'Doe'") + * searchUser("name eq 'John' and surname eq 'Doe'") + * </pre> + * + * @param query query language string starting with {@code user:} + * @return matching {@link IUser} or {@code null} if none is found + */ IUser searchUser(String query) { + query = SearchUtils.ensureStartsWithUser(query) return userSearchService.searchOne(query) } + /** + * Searches for all {@link IUser} instances matching the given query and returns a paged result. + * <p> + * The query must start with the resource keyword {@code users} (plural) and may contain + * paging and sorting clauses. + * </p> + * Example: + * <pre> + * pagedSearchUsers("users: name eq 'John' page 0 size 25 sort by surname asc") + * pagedSearchUsers("users: email contains '@company.com'") + * pagedSearchUsers("email contains '@company.com'") + * </pre> + * + * @param query query language string starting with {@code users:} + * @return {@link Page} of matching users + */ Page<IUser> pagedSearchUsers(String query) { + query = SearchUtils.ensureStartsWithUsers(query) return userSearchService.searchAll(query) } + /** + * Searches for all {@link IUser} instances matching the given query and returns them as a list. + * <p> + * The query must start with the resource keyword {@code users} (plural). This is a convenience + * method returning only the content of {@link #pagedSearchUsers(String)}. + * </p> + * Example: + * <pre> + * searchUsers("users: surname eq 'Doe' sort by name asc") + * searchUsers("users: email in ('a@mail.com', 'b@mail.com')") + * searchUsers("email in ('a@mail.com', 'b@mail.com')") + * </pre> + * + * @param query query language string starting with {@code users:} + * @return list of matching users + */ List<IUser> searchUsers(String query) { + query = SearchUtils.ensureStartsWithUsers(query) return pagedSearchUsers(query).content } + /** + * Counts the number of {@link IUser} instances matching the given query. + * <p> + * The query must start with the resource keyword {@code users} (plural). + * </p> + * Example: + * <pre> + * countUsers("users: email contains '@company.com'") + * countUsers("users: name eq 'John'") + * countUsers("name eq 'John'") + * </pre> + * + * @param query query language string starting with {@code users:} + * @return number of matching users + */ long countUsers(String query) { + query = SearchUtils.ensureStartsWithUsers(query) return userSearchService.count(query) } + /** + * Checks whether at least one {@link IUser} matching the given query exists. + * <p> + * The query must start with the resource keyword {@code users} (plural). + * </p> + * Example: + * <pre> + * existsUser("users: email eq 'user@mail.com'") + * existsUser("users: name eq 'John' and surname eq 'Doe'") + * existsUser("name eq 'John' and surname eq 'Doe'") + * </pre> + * + * @param query query language string starting with {@code users:} + * @return {@code true} if a matching user exists, {@code false} otherwise + */ boolean existsUser(String query) { + query = SearchUtils.ensureStartsWithUsers(query) return userSearchService.exists(query) } + /** + * Generic search that resolves the resource type from the query itself and executes the search. + * <p> + * The query must start with one of the resource keywords: {@code process}/{@code processes}, + * {@code case}/{@code cases}, {@code task}/{@code tasks} or {@code user}/{@code users}. + * When the singular form is used, a single matching instance is returned. When the plural form + * is used, the content (a {@link List}) of the resulting {@link Page} is returned. + * </p> + * Example: + * <pre> + * search("case: processIdentifier eq 'query_test' and data.number_0.value == 3") + * search("cases: processIdentifier eq 'query_test' page 1 size 5 sort by title desc") + * search("process: identifier == 'query_test'") + * </pre> + * + * @param query query language string starting with a resource keyword + * @return a single resource instance (singular form), a {@link List} of instances (plural form), + * or {@code null} if nothing matches + */ Object search(String query) { Object result = searchService.search(query) if (result instanceof Page<?>) { @@ -2784,10 +3130,40 @@ class ActionDelegate { return result } + /** + * Generic count that resolves the resource type from the query itself and counts matching instances. + * <p> + * The query must start with one of the resource keywords: {@code processes}, {@code cases}, + * {@code tasks} or {@code users} (plural form). + * </p> + * Example: + * <pre> + * count("cases: processIdentifier eq 'query_test' and data.boolean_0.value == true") + * count("users: email contains '@company.com'") + * </pre> + * + * @param query query language string starting with a resource keyword + * @return number of matching instances + */ long count(String query) { return searchService.count(query) } + /** + * Generic existence check that resolves the resource type from the query itself. + * <p> + * The query must start with one of the resource keywords: {@code processes}, {@code cases}, + * {@code tasks} or {@code users} (plural form). + * </p> + * Example: + * <pre> + * exists("cases: processIdentifier eq 'query_test'") + * exists("tasks: transitionId eq 't1' and userId eq 'user1'") + * </pre> + * + * @param query query language string starting with a resource keyword + * @return {@code true} if a matching instance exists, {@code false} otherwise + */ boolean exists(String query) { return searchService.exists(query) } diff --git a/src/main/java/com/netgrif/application/engine/pfql/service/IResourceSearchService.java b/src/main/java/com/netgrif/application/engine/pfql/service/IResourceSearchService.java index 41aa7cc11e6..b097588e24f 100644 --- a/src/main/java/com/netgrif/application/engine/pfql/service/IResourceSearchService.java +++ b/src/main/java/com/netgrif/application/engine/pfql/service/IResourceSearchService.java @@ -2,10 +2,7 @@ import com.netgrif.application.engine.pfql.domain.enums.QueryType; -import org.slf4j.Logger; import org.springframework.data.domain.Page; -import org.springframework.data.domain.PageRequest; -import org.springframework.data.domain.Pageable; /** @@ -40,28 +37,48 @@ public interface IResourceSearchService<Resource> { boolean exists(String queryString); boolean exists(QueryLangEvaluator evaluator); - // todo 2443 javadoc + /** + * Validates that the query evaluator is not null. + * + * @param evaluator the query evaluator to validate + * @throws IllegalArgumentException if the evaluator is null + */ default void checkEvaluatorNotNull(QueryLangEvaluator evaluator) { if (evaluator == null) { throw new IllegalArgumentException("Query cannot be null"); } } - // todo 2443 javadoc + /** + * Validates that the query evaluator expects multiple results. + * + * @param evaluator the query evaluator to validate + * @throws IllegalArgumentException if the evaluator expects a single result + */ default void checkEvaluatorIsMultiple(QueryLangEvaluator evaluator) { if (!evaluator.getMultiple()) { throw new IllegalArgumentException("Cannot use searchAll() with a query that expects single result. Use searchOne() instead."); } } - // todo 2443 javadoc + /** + * Validates that the query evaluator expects a single result. + * + * @param evaluator the query evaluator to validate + * @throws IllegalArgumentException if the evaluator expects multiple results + */ default void checkEvaluatorIsSingle(QueryLangEvaluator evaluator) { if (evaluator.getMultiple()) { throw new IllegalArgumentException("Cannot use searchOne() with a query that expects multiple results. Use searchAll() instead."); } } - // todo 2443 javadoc + /** + * Validates that the query evaluator's resource type matches the expected type. + * + * @param evaluator the query evaluator to validate + * @throws IllegalArgumentException if the resource type does not match + */ default void checkEvaluatorResourceType(QueryLangEvaluator evaluator) { if (evaluator.getResourceType() != getQueryResourceType()) { throw new IllegalArgumentException(String.format("Wrong query resource type. Should be: %s, was: %s", diff --git a/src/main/java/com/netgrif/application/engine/pfql/service/caseresource/CaseSearchService.java b/src/main/java/com/netgrif/application/engine/pfql/service/caseresource/CaseSearchService.java index 1a480043c6b..9d9c77265f7 100644 --- a/src/main/java/com/netgrif/application/engine/pfql/service/caseresource/CaseSearchService.java +++ b/src/main/java/com/netgrif/application/engine/pfql/service/caseresource/CaseSearchService.java @@ -30,6 +30,9 @@ @RequiredArgsConstructor public class CaseSearchService implements IResourceSearchService<Case> { + public static final String QUERY_SINGLE_PREFIX = "case: "; + public static final String QUERY_MULTIPLE_PREFIX = "cases: "; + private final IWorkflowService workflowService; private final IElasticCaseService elasticCaseService; private final IUserService userService; diff --git a/src/main/java/com/netgrif/application/engine/pfql/service/processresource/ProcessSearchService.java b/src/main/java/com/netgrif/application/engine/pfql/service/processresource/ProcessSearchService.java index 4f945338032..6529d358e3b 100644 --- a/src/main/java/com/netgrif/application/engine/pfql/service/processresource/ProcessSearchService.java +++ b/src/main/java/com/netgrif/application/engine/pfql/service/processresource/ProcessSearchService.java @@ -20,6 +20,9 @@ @RequiredArgsConstructor public class ProcessSearchService implements IResourceSearchService<PetriNet> { + public static final String QUERY_SINGLE_PREFIX = "process: "; + public static final String QUERY_MULTIPLE_PREFIX = "processes: "; + private final IPetriNetService petriNetService; /** diff --git a/src/main/java/com/netgrif/application/engine/pfql/service/taskresource/TaskSearchService.java b/src/main/java/com/netgrif/application/engine/pfql/service/taskresource/TaskSearchService.java index d34fb17dfdc..fdce84b36e8 100644 --- a/src/main/java/com/netgrif/application/engine/pfql/service/taskresource/TaskSearchService.java +++ b/src/main/java/com/netgrif/application/engine/pfql/service/taskresource/TaskSearchService.java @@ -34,6 +34,9 @@ @RequiredArgsConstructor public class TaskSearchService implements IResourceSearchService<Task> { + public static final String QUERY_SINGLE_PREFIX = "task: "; + public static final String QUERY_MULTIPLE_PREFIX = "tasks: "; + private final ITaskService taskService; private final IElasticTaskService elasticTaskService; private final IUserService userService; diff --git a/src/main/java/com/netgrif/application/engine/pfql/service/userresource/UserSearchService.java b/src/main/java/com/netgrif/application/engine/pfql/service/userresource/UserSearchService.java index ba659c3d508..2ddbb86d8a9 100644 --- a/src/main/java/com/netgrif/application/engine/pfql/service/userresource/UserSearchService.java +++ b/src/main/java/com/netgrif/application/engine/pfql/service/userresource/UserSearchService.java @@ -29,6 +29,9 @@ @RequiredArgsConstructor public class UserSearchService implements IResourceSearchService<IUser> { + public static final String QUERY_SINGLE_PREFIX = "user: "; + public static final String QUERY_MULTIPLE_PREFIX = "users: "; + private final IUserService userService; /** diff --git a/src/main/java/com/netgrif/application/engine/pfql/service/utils/SearchUtils.java b/src/main/java/com/netgrif/application/engine/pfql/service/utils/SearchUtils.java index 2bb468d1e5b..7eac4042e6d 100644 --- a/src/main/java/com/netgrif/application/engine/pfql/service/utils/SearchUtils.java +++ b/src/main/java/com/netgrif/application/engine/pfql/service/utils/SearchUtils.java @@ -10,6 +10,10 @@ import com.netgrif.application.engine.pfql.service.QueryLangErrorListener; import com.netgrif.application.engine.pfql.service.QueryLangEvaluator; import com.netgrif.application.engine.pfql.service.QueryLangExplainEvaluator; +import com.netgrif.application.engine.pfql.service.caseresource.CaseSearchService; +import com.netgrif.application.engine.pfql.service.processresource.ProcessSearchService; +import com.netgrif.application.engine.pfql.service.taskresource.TaskSearchService; +import com.netgrif.application.engine.pfql.service.userresource.UserSearchService; import com.querydsl.core.BooleanBuilder; import com.querydsl.core.types.Predicate; import com.querydsl.core.types.dsl.BooleanExpression; @@ -29,11 +33,19 @@ import java.time.format.DateTimeParseException; import java.util.List; import java.util.Map; +import java.util.Set; import java.util.stream.Collectors; @Slf4j public class SearchUtils { + private static final Set<String> queryPrefixes = Set.of( + CaseSearchService.QUERY_SINGLE_PREFIX, CaseSearchService.QUERY_MULTIPLE_PREFIX, + TaskSearchService.QUERY_SINGLE_PREFIX, TaskSearchService.QUERY_MULTIPLE_PREFIX, + ProcessSearchService.QUERY_SINGLE_PREFIX, ProcessSearchService.QUERY_MULTIPLE_PREFIX, + UserSearchService.QUERY_SINGLE_PREFIX, UserSearchService.QUERY_MULTIPLE_PREFIX + ); + public static final Map<ComparisonType, List<Integer>> comparisonOperators = Map.of( ComparisonType.ID, List.of(QueryLangParser.EQ, QueryLangParser.NEQ, QueryLangParser.IN), ComparisonType.STRING, List.of(QueryLangParser.EQ, QueryLangParser.NEQ, QueryLangParser.CONTAINS, QueryLangParser.LT, QueryLangParser.LTE, QueryLangParser.GT, QueryLangParser.GTE), @@ -438,4 +450,45 @@ public static String buildElasticQueryInRange(String attribute, String leftValue + ")"; return not ? "NOT " + query : query; } + + public static String ensureStartsWithCase(String query) { + return ensureStartsWithPrefix(query, CaseSearchService.QUERY_SINGLE_PREFIX); + } + + public static String ensureStartsWithCases(String query) { + return ensureStartsWithPrefix(query, CaseSearchService.QUERY_MULTIPLE_PREFIX); + } + + public static String ensureStartsWithTask(String query) { + return ensureStartsWithPrefix(query, TaskSearchService.QUERY_SINGLE_PREFIX); + } + + public static String ensureStartsWithTasks(String query) { + return ensureStartsWithPrefix(query, TaskSearchService.QUERY_MULTIPLE_PREFIX); + } + + public static String ensureStartsWithProcess(String query) { + return ensureStartsWithPrefix(query, ProcessSearchService.QUERY_SINGLE_PREFIX); + } + + public static String ensureStartsWithProcesses(String query) { + return ensureStartsWithPrefix(query, ProcessSearchService.QUERY_MULTIPLE_PREFIX); + } + + public static String ensureStartsWithUser(String query) { + return ensureStartsWithPrefix(query, UserSearchService.QUERY_SINGLE_PREFIX); + } + + public static String ensureStartsWithUsers(String query) { + return ensureStartsWithPrefix(query, UserSearchService.QUERY_MULTIPLE_PREFIX); + } + + private static String ensureStartsWithPrefix(String query, String prefix) { + if (query == null) { + return null; + } + boolean hasValidPrefix = queryPrefixes.stream() + .anyMatch(validPrefix -> query.toLowerCase().startsWith(validPrefix.trim().toLowerCase())); + return hasValidPrefix ? query : prefix + query; + } } diff --git a/src/test/groovy/com/netgrif/application/engine/pfql/QueryLangActionTest.groovy b/src/test/groovy/com/netgrif/application/engine/pfql/QueryLangActionTest.groovy index 944266e13e6..871ed934130 100644 --- a/src/test/groovy/com/netgrif/application/engine/pfql/QueryLangActionTest.groovy +++ b/src/test/groovy/com/netgrif/application/engine/pfql/QueryLangActionTest.groovy @@ -82,10 +82,18 @@ class QueryLangActionTest { pressButton(String.format(SEARCH_ONE_TEMPLATE, "process")) assertEquals("class com.netgrif.application.engine.petrinet.domain.PetriNet", testCase.getFieldValue(RESULT_CLASS_FIELD_ID)) + updateQuery("identifier == '" + testProcess.identifier + "'") + pressButton(String.format(SEARCH_ONE_TEMPLATE, "process")) + assertEquals("class com.netgrif.application.engine.petrinet.domain.PetriNet", testCase.getFieldValue(RESULT_CLASS_FIELD_ID)) + updateQuery("processes: identifier == '" + testProcess.identifier + "'") pressButton(String.format(SEARCH_TEMPLATE, "process")) assertEquals("class java.util.Collections\$UnmodifiableRandomAccessList", testCase.getFieldValue(RESULT_CLASS_FIELD_ID)) + updateQuery("identifier == '" + testProcess.identifier + "'") + pressButton(String.format(SEARCH_TEMPLATE, "process")) + assertEquals("class java.util.Collections\$UnmodifiableRandomAccessList", testCase.getFieldValue(RESULT_CLASS_FIELD_ID)) + pressButton(String.format(SEARCH_PAGED_TEMPLATE, "process")) assertEquals("class org.springframework.data.domain.PageImpl", testCase.getFieldValue(RESULT_CLASS_FIELD_ID)) @@ -99,10 +107,18 @@ class QueryLangActionTest { pressButton(String.format(SEARCH_ONE_TEMPLATE, "case")) assertEquals("class com.netgrif.application.engine.workflow.domain.Case", testCase.getFieldValue(RESULT_CLASS_FIELD_ID)) + updateQuery("processIdentifier == '" + testProcess.identifier + "'") + pressButton(String.format(SEARCH_ONE_TEMPLATE, "case")) + assertEquals("class com.netgrif.application.engine.workflow.domain.Case", testCase.getFieldValue(RESULT_CLASS_FIELD_ID)) + updateQuery("cases: processIdentifier == '" + testProcess.identifier + "'") pressButton(String.format(SEARCH_TEMPLATE, "case")) assertEquals("class java.util.Collections\$UnmodifiableRandomAccessList", testCase.getFieldValue(RESULT_CLASS_FIELD_ID)) + updateQuery("processIdentifier == '" + testProcess.identifier + "'") + pressButton(String.format(SEARCH_TEMPLATE, "case")) + assertEquals("class java.util.Collections\$UnmodifiableRandomAccessList", testCase.getFieldValue(RESULT_CLASS_FIELD_ID)) + pressButton(String.format(SEARCH_PAGED_TEMPLATE, "case")) assertEquals("class org.springframework.data.domain.PageImpl", testCase.getFieldValue(RESULT_CLASS_FIELD_ID)) @@ -116,10 +132,18 @@ class QueryLangActionTest { pressButton(String.format(SEARCH_ONE_TEMPLATE, "task")) assertEquals("class com.netgrif.application.engine.workflow.domain.Task", testCase.getFieldValue(RESULT_CLASS_FIELD_ID)) + updateQuery("caseId == '" + testCase.stringId + "'") + pressButton(String.format(SEARCH_ONE_TEMPLATE, "task")) + assertEquals("class com.netgrif.application.engine.workflow.domain.Task", testCase.getFieldValue(RESULT_CLASS_FIELD_ID)) + updateQuery("tasks: caseId == '" + testCase.stringId + "'") pressButton(String.format(SEARCH_TEMPLATE, "task")) assertEquals("class java.util.Collections\$UnmodifiableRandomAccessList", testCase.getFieldValue(RESULT_CLASS_FIELD_ID)) + updateQuery("caseId == '" + testCase.stringId + "'") + pressButton(String.format(SEARCH_TEMPLATE, "task")) + assertEquals("class java.util.Collections\$UnmodifiableRandomAccessList", testCase.getFieldValue(RESULT_CLASS_FIELD_ID)) + pressButton(String.format(SEARCH_PAGED_TEMPLATE, "task")) assertEquals("class org.springframework.data.domain.PageImpl", testCase.getFieldValue(RESULT_CLASS_FIELD_ID)) @@ -133,10 +157,18 @@ class QueryLangActionTest { pressButton(String.format(SEARCH_ONE_TEMPLATE, "user")) assertEquals("class com.netgrif.application.engine.auth.domain.User", testCase.getFieldValue(RESULT_CLASS_FIELD_ID)) + updateQuery("email == '" + superCreator.superUser.email + "'") + pressButton(String.format(SEARCH_ONE_TEMPLATE, "user")) + assertEquals("class com.netgrif.application.engine.auth.domain.User", testCase.getFieldValue(RESULT_CLASS_FIELD_ID)) + updateQuery("users: email == '" + superCreator.superUser.email + "'") pressButton(String.format(SEARCH_TEMPLATE, "user")) assertEquals("class java.util.Collections\$UnmodifiableRandomAccessList", testCase.getFieldValue(RESULT_CLASS_FIELD_ID)) + updateQuery("email == '" + superCreator.superUser.email + "'") + pressButton(String.format(SEARCH_TEMPLATE, "user")) + assertEquals("class java.util.Collections\$UnmodifiableRandomAccessList", testCase.getFieldValue(RESULT_CLASS_FIELD_ID)) + pressButton(String.format(SEARCH_PAGED_TEMPLATE, "user")) assertEquals("class org.springframework.data.domain.PageImpl", testCase.getFieldValue(RESULT_CLASS_FIELD_ID)) From 29ea7b75036d2ee2cce7c5333996045d6580aa02 Mon Sep 17 00:00:00 2001 From: chvostek <chvostek@netgrif.com> Date: Mon, 8 Jun 2026 20:13:16 +0200 Subject: [PATCH 14/17] [NAE-2443] PFQL support - rework assertions in QueryLangTest --- .../engine/pfql/QueryLangTest.java | 961 +++++++++--------- 1 file changed, 486 insertions(+), 475 deletions(-) diff --git a/src/test/java/com/netgrif/application/engine/pfql/QueryLangTest.java b/src/test/java/com/netgrif/application/engine/pfql/QueryLangTest.java index a3777c97382..dfe7f754c25 100644 --- a/src/test/java/com/netgrif/application/engine/pfql/QueryLangTest.java +++ b/src/test/java/com/netgrif/application/engine/pfql/QueryLangTest.java @@ -17,15 +17,14 @@ 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.Page; +import org.springframework.data.domain.PageImpl; import org.springframework.data.domain.Pageable; import org.springframework.data.domain.Sort; import org.springframework.data.mongodb.core.MongoOperations; @@ -40,58 +39,72 @@ import java.util.Optional; import static com.netgrif.application.engine.pfql.service.utils.SearchUtils.evaluateQuery; +import static org.junit.jupiter.api.Assertions.*; -@Slf4j @SpringBootTest @ActiveProfiles({"test"}) @ExtendWith(SpringExtension.class) public class QueryLangTest { + public static final ObjectId GENERIC_OBJECT_ID = ObjectId.get(); @Autowired - MongoOperations mongoOperations; + private MongoOperations mongoOperations; + @Autowired - ISearchService searchService; + private ISearchService searchService; + @Autowired - ImportHelper helper; + private ImportHelper helper; + @Autowired - TestHelper testHelper; + private TestHelper testHelper; @Test + @SuppressWarnings("unchecked") public void testSearchService() { testHelper.truncateDbs(); Optional<PetriNet> optionalPetriNet = helper.createNet("/pfql.xml"); - if (optionalPetriNet.isEmpty()) { - throw new RuntimeException("query_test.xml net not created"); - } + assertNotNull(optionalPetriNet); - Object process = searchService.search("process: identifier == 'query_test'"); - if (!(process instanceof PetriNet)) { - throw new RuntimeException("Process not found"); - } - PetriNet net = (PetriNet) process; + Object processAsObject = searchService.search("process: identifier == 'query_test'"); + assertNotNull(processAsObject); + assertEquals(PetriNet.class, processAsObject.getClass()); + + PetriNet process = (PetriNet) processAsObject; for (int i = 0; i < 10; i++) { Map<String, String> params = new HashMap<>(); - params.put("id", ""+i); - helper.createCase(String.format("Test %02d", i), net, params); + params.put("id", String.valueOf(i)); + helper.createCase(String.format("Test %02d", i), process, params); } Object cases = searchService.search("cases: processIdentifier eq 'query_test' page 1 size 5 sort by title desc"); - log.info("Cases: {}", cases); - - Object case_3 = searchService.search("case: processIdentifier eq 'query_test' and data.number_0.value == 3"); - log.info("Case with number_0 == 3: {}", ((Case)case_3).getTitle()); - Object case_4 = searchService.search("case: processIdentifier eq 'query_test' and data.text_0.value == '4'"); - log.info("Case with text_0 == '4': {}", ((Case)case_4).getTitle()); - Object case_5 = searchService.search("case: processIdentifier eq 'query_test' and data.boolean_0.value == true"); - log.info("Case with boolean_0 == true: {}", ((Case)case_5).getTitle()); + assertNotNull(cases); + assertEquals(PageImpl.class, cases.getClass()); + assertEquals(10, ((Page<Case>) cases).getTotalElements()); + + Object case3 = searchService.search("case: processIdentifier eq 'query_test' and data.number_0.value == 3"); + assertNotNull(case3); + assertEquals(Case.class, case3.getClass()); + assertEquals("Test 03", ((Case) case3).getTitle()); + assertEquals(3, ((Case) case3).getFieldValue("number_0")); + + Object case4 = searchService.search("case: processIdentifier eq 'query_test' and data.text_0.value == '4'"); + assertNotNull(case4); + assertEquals(Case.class, case4.getClass()); + assertEquals("4", ((Case) case4).getFieldValue("text_0")); + + Object case5 = searchService.search("case: processIdentifier eq 'query_test' and data.boolean_0.value == true"); + assertNotNull(case5); + assertEquals(Case.class, case5.getClass()); + assertEquals(true, ((Case) case5).getFieldValue("boolean_0")); cases = searchService.search("cases: processIdentifier eq 'query_test' and data.boolean_0.value == true"); - assert ((Page<Case>)cases).getTotalElements() == 5; + assertEquals(5, ((Page<Case>) cases).getTotalElements()); cases = searchService.search("cases: processIdentifier eq 'query_test' and data.boolean_0.value == true and data.text_0.value != '4'"); - assert ((Page<Case>)cases).getTotalElements() == 4; + assertEquals(4, ((Page<Case>) cases).getTotalElements()); } @Test @@ -321,62 +334,62 @@ public void testSimpleMongodbCaseQuery() { // only available for elastic query // places comparison actual = evaluateQuery("case: places.p1.marking eq 1").getFullMongoQuery(); - assert actual == null; + assertNull(actual); // task state comparison actual = evaluateQuery("case: tasks.t1.state eq enabled").getFullMongoQuery(); - assert actual == null; + assertNull(actual); // task userId comparison actual = evaluateQuery("case: tasks.t1.userId eq 'test'").getFullMongoQuery(); - assert actual == null; + assertNull(actual); // data value comparison actual = evaluateQuery("case: data.field1.value eq 'test'").getFullMongoQuery(); - assert actual == null; + assertNull(actual); actual = evaluateQuery("case: data.field1.value contains 'test'").getFullMongoQuery(); - assert actual == null; + assertNull(actual); actual = evaluateQuery("case: data.field1.value eq 1").getFullMongoQuery(); - assert actual == null; + assertNull(actual); actual = evaluateQuery("case: data.field1.value lt 1").getFullMongoQuery(); - assert actual == null; + assertNull(actual); actual = evaluateQuery("case: data.field1.value lte 1").getFullMongoQuery(); - assert actual == null; + assertNull(actual); actual = evaluateQuery("case: data.field1.value gt 1").getFullMongoQuery(); - assert actual == null; + assertNull(actual); actual = evaluateQuery("case: data.field1.value gte 1").getFullMongoQuery(); - assert actual == null; + assertNull(actual); actual = evaluateQuery("case: data.field1.value eq 2011-12-03T10:15:30").getFullMongoQuery(); - assert actual == null; + assertNull(actual); actual = evaluateQuery("case: data.field1.value lt 2011-12-03T10:15:30").getFullMongoQuery(); - assert actual == null; + assertNull(actual); actual = evaluateQuery("case: data.field1.value lte 2011-12-03T10:15:30").getFullMongoQuery(); - assert actual == null; + assertNull(actual); actual = evaluateQuery("case: data.field1.value gt 2011-12-03T10:15:30").getFullMongoQuery(); - assert actual == null; + assertNull(actual); actual = evaluateQuery("case: data.field1.value gte 2011-12-03T10:15:30").getFullMongoQuery(); - assert actual == null; + assertNull(actual); actual = evaluateQuery("case: data.field1.value eq true").getFullMongoQuery(); - assert actual == null; + assertNull(actual); // data options comparison actual = evaluateQuery("case: data.field1.options eq 'test'").getFullMongoQuery(); - assert actual == null; + assertNull(actual); actual = evaluateQuery("case: data.field1.options contains 'test'").getFullMongoQuery(); - assert actual == null; + assertNull(actual); } @Test @@ -702,62 +715,62 @@ 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; + assertNull(actual); // identifier comparison actual = evaluateQuery("process: identifier eq 'test'").getFullElasticQuery(); - assert actual == null; + assertNull(actual); actual = evaluateQuery("process: identifier contains 'test'").getFullElasticQuery(); - assert actual == null; + assertNull(actual); // version comparison actual = evaluateQuery("process: version eq 1.1.1").getFullElasticQuery(); - assert actual == null; + assertNull(actual); actual = evaluateQuery("process: version lt 1.1.1").getFullElasticQuery(); - assert actual == null; + assertNull(actual); actual = evaluateQuery("process: version lte 1.1.1").getFullElasticQuery(); - assert actual == null; + assertNull(actual); actual = evaluateQuery("process: version gt 1.1.1").getFullElasticQuery(); - assert actual == null; + assertNull(actual); actual = evaluateQuery("process: version gte 1.1.1").getFullElasticQuery(); - assert actual == null; + assertNull(actual); // title comparison actual = evaluateQuery("process: title eq 'test'").getFullElasticQuery(); - assert actual == null; + assertNull(actual); actual = evaluateQuery("process: title contains 'test'").getFullElasticQuery(); - assert actual == null; + assertNull(actual); // creationDate comparison actual = evaluateQuery("process: creationDate eq 2011-12-03T10:15:30").getFullElasticQuery(); - assert actual == null; + assertNull(actual); actual = evaluateQuery("process: creationDate lt 2011-12-03T10:15:30").getFullElasticQuery(); - assert actual == null; + assertNull(actual); actual = evaluateQuery("process: creationDate lte 2011-12-03T10:15:30").getFullElasticQuery(); - assert actual == null; + assertNull(actual); actual = evaluateQuery("process: creationDate gt 2011-12-03T10:15:30").getFullElasticQuery(); - assert actual == null; + assertNull(actual); actual = evaluateQuery("process: creationDate gte 2011-12-03T10:15:30").getFullElasticQuery(); - assert actual == null; + assertNull(actual); } @Test @@ -765,45 +778,46 @@ 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; + assertNull(actual); + assertNull(actual); actual = evaluateQuery(String.format("process: id neq '%s'", GENERIC_OBJECT_ID)).getFullElasticQuery(); - assert actual == null; + assertNull(actual); // and comparison actual = evaluateQuery(String.format("process: id eq '%s' and title eq 'test'", GENERIC_OBJECT_ID)).getFullElasticQuery(); - assert actual == null; + assertNull(actual); // and not comparison actual = evaluateQuery(String.format("process: id eq '%s' and title not eq 'test'", GENERIC_OBJECT_ID)).getFullElasticQuery(); - assert actual == null; + assertNull(actual); actual = evaluateQuery(String.format("process: id eq '%s' and title != 'test'", GENERIC_OBJECT_ID)).getFullElasticQuery(); - assert actual == null; + assertNull(actual); // or comparison actual = evaluateQuery(String.format("process: id eq '%s' or title eq 'test'", GENERIC_OBJECT_ID)).getFullElasticQuery(); - assert actual == null; + assertNull(actual); // or not comparison actual = evaluateQuery(String.format("process: id eq '%s' or title not eq 'test'", GENERIC_OBJECT_ID)).getFullElasticQuery(); - assert actual == null; + assertNull(actual); actual = evaluateQuery(String.format("process: id eq '%s' or title != 'test'", GENERIC_OBJECT_ID)).getFullElasticQuery(); - assert actual == null; + assertNull(actual); // 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; + assertNull(actual); // 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; + assertNull(actual); // 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; + assertNull(actual); } @Test @@ -811,20 +825,20 @@ 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); + assertEquals(expected, 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); + assertEquals(expected, 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); + assertEquals(expected, 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); + assertEquals(expected, actual); // processIdentifier comparison checkStringComparisonElastic("case", "processIdentifier", "processIdentifier"); @@ -838,15 +852,15 @@ public void testSimpleElasticCaseQuery() { // author comparison actual = evaluateQuery("case: author eq 'test'").getFullElasticQuery(); expected = "author:test"; - assert expected.equals(actual); + assertEquals(expected, actual); actual = evaluateQuery("case: author contains 'test'").getFullElasticQuery(); expected = "author:*test*"; - assert expected.equals(actual); + assertEquals(expected, 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); + assertEquals(expected, actual); // places comparison checkNumberComparisonElastic("case", "places.p1.marking", "places.p1.marking"); @@ -855,23 +869,23 @@ public void testSimpleElasticCaseQuery() { // TODO: fix // actual = evaluateQuery("case: tasks.t1.state eq enabled").getFullElasticQuery(); // expected = String.format("tasks.t1.state:%s", State.ENABLED); -// assert expected.equals(actual); +// assertEquals(expected, actual); // actual = evaluateQuery("case: tasks.t1.state eq disabled").getFullElasticQuery(); // expected = String.format("tasks.t1.state:%s", State.DISABLED); -// assert expected.equals(actual); +// assertEquals(expected, actual); // task userId comparison actual = evaluateQuery("case: tasks.t1.userId eq 'test'").getFullElasticQuery(); expected = "tasks.t1.userId:test"; - assert expected.equals(actual); + assertEquals(expected, actual); actual = evaluateQuery("case: tasks.t1.userId contains 'test'").getFullElasticQuery(); expected = "tasks.t1.userId:*test*"; - assert expected.equals(actual); + assertEquals(expected, 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); + assertEquals(expected, actual); // data value comparison checkStringComparisonElastic("case", "data.field1.value", "dataSet.field1.textValue"); @@ -882,11 +896,11 @@ public void testSimpleElasticCaseQuery() { actual = evaluateQuery("case: data.field1.value eq true").getFullElasticQuery(); expected = "dataSet.field1.booleanValue:true"; - assert expected.equals(actual); + assertEquals(expected, actual); actual = evaluateQuery("case: data.field1.value eq false").getFullElasticQuery(); expected = "dataSet.field1.booleanValue:false"; - assert expected.equals(actual); + assertEquals(expected, actual); // data options comparison checkStringComparisonElastic("case", "data.field1.options", "dataSet.field1.options"); @@ -897,49 +911,49 @@ 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); + assertEquals(expected, actual); actual = evaluateQuery(String.format("case: id neq '%s'", GENERIC_OBJECT_ID)).getFullElasticQuery(); expected = String.format("NOT stringId:%s", GENERIC_OBJECT_ID); - assert expected.equals(actual); + assertEquals(expected, 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); + assertEquals(expected, 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); + assertEquals(expected, actual); actual = evaluateQuery(String.format("case: id eq '%s' and title != 'test'", GENERIC_OBJECT_ID)).getFullElasticQuery(); expected = String.format("stringId:%s AND NOT title:test", GENERIC_OBJECT_ID); - assert expected.equals(actual); + assertEquals(expected, 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); + assertEquals(expected, 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); + assertEquals(expected, actual); actual = evaluateQuery(String.format("case: id eq '%s' or title neq 'test'", GENERIC_OBJECT_ID)).getFullElasticQuery(); expected = String.format("stringId:%s OR NOT title:test", GENERIC_OBJECT_ID); - assert expected.equals(actual); + assertEquals(expected, 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); + assertEquals(expected, 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); + assertEquals(expected, actual); } @Test @@ -947,81 +961,81 @@ 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; + assertNull(actual); // transitionId comparison actual = evaluateQuery("task: transitionId eq 'test'").getFullElasticQuery(); - assert actual == null; + assertNull(actual); actual = evaluateQuery("task: transitionId contains 'test'").getFullElasticQuery(); - assert actual == null; + assertNull(actual); // title comparison actual = evaluateQuery("task: title eq 'test'").getFullElasticQuery(); - assert actual == null; + assertNull(actual); actual = evaluateQuery("task: title contains 'test'").getFullElasticQuery(); - assert actual == null; + assertNull(actual); // state comparison actual = evaluateQuery("task: state eq enabled").getFullElasticQuery(); - assert actual == null; + assertNull(actual); actual = evaluateQuery("task: state eq disabled").getFullElasticQuery(); - assert actual == null; + assertNull(actual); // userId comparison actual = evaluateQuery("task: userId eq 'test'").getFullElasticQuery(); - assert actual == null; + assertNull(actual); actual = evaluateQuery("task: userId contains 'test'").getFullElasticQuery(); - assert actual == null; + assertNull(actual); // caseId comparison actual = evaluateQuery("task: caseId eq 'test'").getFullElasticQuery(); - assert actual == null; + assertNull(actual); actual = evaluateQuery("task: caseId contains 'test'").getFullElasticQuery(); - assert actual == null; + assertNull(actual); // processId comparison actual = evaluateQuery("task: processId eq 'test'").getFullElasticQuery(); - assert actual == null; + assertNull(actual); actual = evaluateQuery("task: processId contains 'test'").getFullElasticQuery(); - assert actual == null; + assertNull(actual); // lastAssign comparison actual = evaluateQuery("task: lastAssign eq 2011-12-03T10:15:30").getFullElasticQuery(); - assert actual == null; + assertNull(actual); actual = evaluateQuery("task: lastAssign lt 2011-12-03T10:15:30").getFullElasticQuery(); - assert actual == null; + assertNull(actual); actual = evaluateQuery("task: lastAssign lte 2011-12-03T10:15:30").getFullElasticQuery(); - assert actual == null; + assertNull(actual); actual = evaluateQuery("task: lastAssign gt 2011-12-03T10:15:30").getFullElasticQuery(); - assert actual == null; + assertNull(actual); actual = evaluateQuery("task: lastAssign gte 2011-12-03T10:15:30").getFullElasticQuery(); - assert actual == null; + assertNull(actual); // lastFinish comparison actual = evaluateQuery("task: lastFinish eq 2011-12-03T10:15:30").getFullElasticQuery(); - assert actual == null; + assertNull(actual); actual = evaluateQuery("task: lastFinish lt 2011-12-03T10:15:30").getFullElasticQuery(); - assert actual == null; + assertNull(actual); actual = evaluateQuery("task: lastFinish lte 2011-12-03T10:15:30").getFullElasticQuery(); - assert actual == null; + assertNull(actual); actual = evaluateQuery("task: lastFinish gt 2011-12-03T10:15:30").getFullElasticQuery(); - assert actual == null; + assertNull(actual); actual = evaluateQuery("task: lastFinish gte 2011-12-03T10:15:30").getFullElasticQuery(); - assert actual == null; + assertNull(actual); } @Test @@ -1029,44 +1043,44 @@ 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; + assertNull(actual); actual = evaluateQuery(String.format("task: id neq '%s'", GENERIC_OBJECT_ID)).getFullElasticQuery(); - assert actual == null; + assertNull(actual); // and comparison actual = evaluateQuery(String.format("task: id eq '%s' and title eq 'test'", GENERIC_OBJECT_ID)).getFullElasticQuery(); - assert actual == null; + assertNull(actual); // and not comparison actual = evaluateQuery(String.format("task: id eq '%s' and title not eq 'test'", GENERIC_OBJECT_ID)).getFullElasticQuery(); - assert actual == null; + assertNull(actual); actual = evaluateQuery(String.format("task: id eq '%s' and title != 'test'", GENERIC_OBJECT_ID)).getFullElasticQuery(); - assert actual == null; + assertNull(actual); // or comparison actual = evaluateQuery(String.format("task: id eq '%s' or title eq 'test'", GENERIC_OBJECT_ID)).getFullElasticQuery(); - assert actual == null; + assertNull(actual); // or not comparison actual = evaluateQuery(String.format("task: id eq '%s' or title not eq 'test'", GENERIC_OBJECT_ID)).getFullElasticQuery(); - assert actual == null; + assertNull(actual); actual = evaluateQuery(String.format("task: id eq '%s' or title neq 'test'", GENERIC_OBJECT_ID)).getFullElasticQuery(); - assert actual == null; + assertNull(actual); // 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; + assertNull(actual); // 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; + assertNull(actual); // 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; + assertNull(actual); } @Test @@ -1074,28 +1088,28 @@ 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; + assertNull(actual); // name comparison actual = evaluateQuery("user: name eq 'test'").getFullElasticQuery(); - assert actual == null; + assertNull(actual); actual = evaluateQuery("user: name contains 'test'").getFullElasticQuery(); - assert actual == null; + assertNull(actual); // surname comparison actual = evaluateQuery("user: surname eq 'test'").getFullElasticQuery(); - assert actual == null; + assertNull(actual); actual = evaluateQuery("user: surname contains 'test'").getFullElasticQuery(); - assert actual == null; + assertNull(actual); // email comparison actual = evaluateQuery("user: email eq 'test'").getFullElasticQuery(); - assert actual == null; + assertNull(actual); actual = evaluateQuery("user: email contains 'test'").getFullElasticQuery(); - assert actual == null; + assertNull(actual); } @Test @@ -1103,573 +1117,570 @@ 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; + assertNull(actual); actual = evaluateQuery(String.format("user: id != '%s'", GENERIC_OBJECT_ID)).getFullElasticQuery(); - assert actual == null; + assertNull(actual); // and comparison actual = evaluateQuery(String.format("user: id eq '%s' and email eq 'test'", GENERIC_OBJECT_ID)).getFullElasticQuery(); - assert actual == null; + assertNull(actual); // and not comparison actual = evaluateQuery(String.format("user: id eq '%s' and email not eq 'test'", GENERIC_OBJECT_ID)).getFullElasticQuery(); - assert actual == null; + assertNull(actual); actual = evaluateQuery(String.format("user: id eq '%s' and email neq 'test'", GENERIC_OBJECT_ID)).getFullElasticQuery(); - assert actual == null; + assertNull(actual); // or comparison actual = evaluateQuery(String.format("user: id eq '%s' or email eq 'test'", GENERIC_OBJECT_ID)).getFullElasticQuery(); - assert actual == null; + assertNull(actual); // or not comparison actual = evaluateQuery(String.format("user: id eq '%s' or email not eq 'test'", GENERIC_OBJECT_ID)).getFullElasticQuery(); - assert actual == null; + assertNull(actual); actual = evaluateQuery(String.format("user: id eq '%s' or email != 'test'", GENERIC_OBJECT_ID)).getFullElasticQuery(); - assert actual == null; + assertNull(actual); // 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; + assertNull(actual); // 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; + assertNull(actual); // 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; + assertNull(actual); } @Test public void testPagingQuery() { - // default Pageable pageable = evaluateQuery("cases: processIdentifier eq 'test'").getPageable(); - assert pageable.getPageNumber() == 0; - assert pageable.getPageSize() == 20; + assertEquals(0, pageable.getPageNumber()); + assertEquals(20, pageable.getPageSize()); - // page number only pageable = evaluateQuery("cases: processIdentifier eq 'test' page 2").getPageable(); - assert pageable.getPageNumber() == 2; - assert pageable.getPageSize() == 20; + assertEquals(2, pageable.getPageNumber()); + assertEquals(20, pageable.getPageSize()); - // page number and page size pageable = evaluateQuery("cases: processIdentifier eq 'test' page 2 size 4").getPageable(); - assert pageable.getPageNumber() == 2; - assert pageable.getPageSize() == 4; + assertEquals(2, pageable.getPageNumber()); + assertEquals(4, pageable.getPageSize()); } @Test public void testProcessSortingQuery() { // default (no sort) Pageable actual = evaluateQuery("processes: identifier eq 'test'").getPageable(); - assert !actual.getSort().isSorted(); + assertFalse(actual.getSort().isSorted()); // default ordering asc actual = evaluateQuery("processes: identifier eq 'test' sort by id").getPageable(); - assert actual.getSort().isSorted(); + assertTrue(actual.getSort().isSorted()); List<Sort.Order> orders = actual.getSort().toList(); - assert orders.size() == 1; - assert orders.get(0).getProperty().equals("id"); - assert orders.get(0).getDirection() == Sort.Direction.ASC; + assertEquals(1, orders.size()); + assertEquals("id", orders.get(0).getProperty()); + assertEquals(Sort.Direction.ASC, orders.get(0).getDirection()); // set ordering actual = evaluateQuery("processes: identifier eq 'test' sort by id desc").getPageable(); - assert actual.getSort().isSorted(); + assertTrue(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; + assertEquals(1, orders.size()); + assertEquals("id", orders.get(0).getProperty()); + assertEquals(Sort.Direction.DESC, orders.get(0).getDirection()); actual = evaluateQuery("processes: identifier eq 'test' sort by identifier desc").getPageable(); - assert actual.getSort().isSorted(); + assertTrue(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; + assertEquals(1, orders.size()); + assertEquals("identifier", orders.get(0).getProperty()); + assertEquals(Sort.Direction.DESC, orders.get(0).getDirection()); actual = evaluateQuery("processes: identifier eq 'test' sort by title asc").getPageable(); - assert actual.getSort().isSorted(); + assertTrue(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; + assertEquals(1, orders.size()); + assertEquals("title.defaultValue", orders.get(0).getProperty()); + assertEquals(Sort.Direction.ASC, orders.get(0).getDirection()); actual = evaluateQuery("processes: identifier eq 'test' sort by version asc").getPageable(); - assert actual.getSort().isSorted(); + assertTrue(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; + assertEquals(1, orders.size()); + assertEquals("version", orders.get(0).getProperty()); + assertEquals(Sort.Direction.ASC, orders.get(0).getDirection()); actual = evaluateQuery("processes: identifier eq 'test' sort by creationDate asc").getPageable(); - assert actual.getSort().isSorted(); + assertTrue(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; + assertEquals(1, orders.size()); + assertEquals("creationDate", orders.get(0).getProperty()); + assertEquals(Sort.Direction.ASC, orders.get(0).getDirection()); // complex set ordering actual = evaluateQuery("processes: identifier eq 'test' sort by id asc, title desc").getPageable(); - assert actual.getSort().isSorted(); + assertTrue(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); + assertEquals(2, orders.size()); + assertEquals("id", orders.get(0).getProperty()); + assertEquals(Sort.Direction.ASC, orders.get(0).getDirection()); + assertEquals("title.defaultValue", orders.get(1).getProperty()); + assertEquals(Sort.Direction.DESC, orders.get(1).getDirection()); // complex default ordering actual = evaluateQuery("processes: identifier eq 'test' sort by id asc, title").getPageable(); - assert actual.getSort().isSorted(); + assertTrue(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); + assertEquals(2, orders.size()); + assertEquals("id", orders.get(0).getProperty()); + assertEquals(Sort.Direction.ASC, orders.get(0).getDirection()); + assertEquals("title.defaultValue", orders.get(1).getProperty()); + assertEquals(Sort.Direction.ASC, orders.get(1).getDirection()); } @Test public void testCaseSortingMongoDbQuery() { // default (no sort) Pageable actual = evaluateQuery("cases: processIdentifier eq 'test'").getPageable(); - assert !actual.getSort().isSorted(); + assertFalse(actual.getSort().isSorted()); // default ordering asc actual = evaluateQuery("cases: processIdentifier eq 'test' sort by id").getPageable(); - assert actual.getSort().isSorted(); + assertTrue(actual.getSort().isSorted()); List<Sort.Order> orders = actual.getSort().toList(); - assert orders.size() == 1; - assert orders.get(0).getProperty().equals("id"); - assert orders.get(0).getDirection() == Sort.Direction.ASC; + assertEquals(1, orders.size()); + assertEquals("id", orders.get(0).getProperty()); + assertEquals(Sort.Direction.ASC, orders.get(0).getDirection()); // set ordering actual = evaluateQuery("cases: processIdentifier eq 'test' sort by id desc").getPageable(); - assert actual.getSort().isSorted(); + assertTrue(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; + assertEquals(1, orders.size()); + assertEquals("id", orders.get(0).getProperty()); + assertEquals(Sort.Direction.DESC, orders.get(0).getDirection()); actual = evaluateQuery("cases: processIdentifier eq 'test' sort by processIdentifier desc").getPageable(); - assert actual.getSort().isSorted(); + assertTrue(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; + assertEquals(1, orders.size()); + assertEquals("processIdentifier", orders.get(0).getProperty()); + assertEquals(Sort.Direction.DESC, orders.get(0).getDirection()); actual = evaluateQuery("cases: processIdentifier eq 'test' sort by title asc").getPageable(); - assert actual.getSort().isSorted(); + assertTrue(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; + assertEquals(1, orders.size()); + assertEquals("title", orders.get(0).getProperty()); + assertEquals(Sort.Direction.ASC, orders.get(0).getDirection()); actual = evaluateQuery("cases: processIdentifier eq 'test' sort by processId asc").getPageable(); - assert actual.getSort().isSorted(); + assertTrue(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; + assertEquals(1, orders.size()); + assertEquals("petriNetObjectId", orders.get(0).getProperty()); + assertEquals(Sort.Direction.ASC, orders.get(0).getDirection()); actual = evaluateQuery("cases: processIdentifier eq 'test' sort by creationDate asc").getPageable(); - assert actual.getSort().isSorted(); + assertTrue(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; + assertEquals(1, orders.size()); + assertEquals("creationDate", orders.get(0).getProperty()); + assertEquals(Sort.Direction.ASC, orders.get(0).getDirection()); actual = evaluateQuery("cases: processIdentifier eq 'test' sort by author desc").getPageable(); - assert actual.getSort().isSorted(); + assertTrue(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; + assertEquals(1, orders.size()); + assertEquals("author.id", orders.get(0).getProperty()); + assertEquals(Sort.Direction.DESC, orders.get(0).getDirection()); // complex set ordering actual = evaluateQuery("cases: processIdentifier eq 'test' sort by id asc, title desc").getPageable(); - assert actual.getSort().isSorted(); + assertTrue(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); + assertEquals(2, orders.size()); + assertEquals("id", orders.get(0).getProperty()); + assertEquals(Sort.Direction.ASC, orders.get(0).getDirection()); + assertEquals("title", orders.get(1).getProperty()); + assertEquals(Sort.Direction.DESC, orders.get(1).getDirection()); // complex default ordering actual = evaluateQuery("cases: processIdentifier eq 'test' sort by id asc, title").getPageable(); - assert actual.getSort().isSorted(); + assertTrue(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); + assertEquals(2, orders.size()); + assertEquals("id", orders.get(0).getProperty()); + assertEquals(Sort.Direction.ASC, orders.get(0).getDirection()); + assertEquals("title", orders.get(1).getProperty()); + assertEquals(Sort.Direction.ASC, orders.get(1).getDirection()); } @Test public void testCaseSortingElasticQuery() { // default (no sort) Pageable actual = evaluateQuery("cases: data.field1.value eq 'test'").getPageable(); - assert !actual.getSort().isSorted(); + assertFalse(actual.getSort().isSorted()); // default ordering asc actual = evaluateQuery("cases: data.field1.value eq 'test' sort by id").getPageable(); - assert actual.getSort().isSorted(); + assertTrue(actual.getSort().isSorted()); List<Sort.Order> orders = actual.getSort().toList(); - assert orders.size() == 1; - assert orders.get(0).getProperty().equals("stringId.keyword"); - assert orders.get(0).getDirection() == Sort.Direction.ASC; + assertEquals(1, orders.size()); + assertEquals("stringId.keyword", orders.get(0).getProperty()); + assertEquals(Sort.Direction.ASC, orders.get(0).getDirection()); // set ordering actual = evaluateQuery("cases: data.field1.value eq 'test' sort by id desc").getPageable(); - assert actual.getSort().isSorted(); + assertTrue(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; + assertEquals(1, orders.size()); + assertEquals("stringId.keyword", orders.get(0).getProperty()); + assertEquals(Sort.Direction.DESC, orders.get(0).getDirection()); actual = evaluateQuery("cases: data.field1.value eq 'test' sort by processIdentifier desc").getPageable(); - assert actual.getSort().isSorted(); + assertTrue(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; + assertEquals(1, orders.size()); + assertEquals("processIdentifier.keyword", orders.get(0).getProperty()); + assertEquals(Sort.Direction.DESC, orders.get(0).getDirection()); actual = evaluateQuery("cases: data.field1.value eq 'test' sort by title asc").getPageable(); - assert actual.getSort().isSorted(); + assertTrue(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; + assertEquals(1, orders.size()); + assertEquals("title.keyword", orders.get(0).getProperty()); + assertEquals(Sort.Direction.ASC, orders.get(0).getDirection()); actual = evaluateQuery("cases: data.field1.value eq 'test' sort by processId asc").getPageable(); - assert actual.getSort().isSorted(); + assertTrue(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; + assertEquals(1, orders.size()); + assertEquals("processId.keyword", orders.get(0).getProperty()); + assertEquals(Sort.Direction.ASC, orders.get(0).getDirection()); actual = evaluateQuery("cases: data.field1.value eq 'test' sort by creationDate asc").getPageable(); - assert actual.getSort().isSorted(); + assertTrue(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; + assertEquals(1, orders.size()); + assertEquals("creationDateSortable", orders.get(0).getProperty()); + assertEquals(Sort.Direction.ASC, orders.get(0).getDirection()); actual = evaluateQuery("cases: data.field1.value eq 'test' sort by author desc").getPageable(); - assert actual.getSort().isSorted(); + assertTrue(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; + assertEquals(1, orders.size()); + assertEquals("author.keyword", orders.get(0).getProperty()); + assertEquals(Sort.Direction.DESC, orders.get(0).getDirection()); actual = evaluateQuery("cases: data.field1.value eq 'test' sort by places.p1.marking desc").getPageable(); - assert actual.getSort().isSorted(); + assertTrue(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; + assertEquals(1, orders.size()); + assertEquals("places.p1.marking", orders.get(0).getProperty()); + assertEquals(Sort.Direction.DESC, orders.get(0).getDirection()); actual = evaluateQuery("cases: data.field1.value eq 'test' sort by tasks.t1.state desc").getPageable(); - assert actual.getSort().isSorted(); + assertTrue(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; + assertEquals(1, orders.size()); + assertEquals("tasks.t1.state.keyword", orders.get(0).getProperty()); + assertEquals(Sort.Direction.DESC, orders.get(0).getDirection()); actual = evaluateQuery("cases: data.field1.value eq 'test' sort by tasks.t1.userId desc").getPageable(); - assert actual.getSort().isSorted(); + assertTrue(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; + assertEquals(1, orders.size()); + assertEquals("tasks.t1.userId.keyword", orders.get(0).getProperty()); + assertEquals(Sort.Direction.DESC, orders.get(0).getDirection()); // complex set ordering actual = evaluateQuery("cases: data.field1.value eq 'test' sort by id asc, title desc").getPageable(); - assert actual.getSort().isSorted(); + assertTrue(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); + assertEquals(2, orders.size()); + assertEquals("stringId.keyword", orders.get(0).getProperty()); + assertEquals(Sort.Direction.ASC, orders.get(0).getDirection()); + assertEquals("title.keyword", orders.get(1).getProperty()); + assertEquals(Sort.Direction.DESC, orders.get(1).getDirection()); // complex default ordering actual = evaluateQuery("cases: data.field1.value eq 'test' sort by id asc, title").getPageable(); - assert actual.getSort().isSorted(); + assertTrue(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); + assertEquals(2, orders.size()); + assertEquals("stringId.keyword", orders.get(0).getProperty()); + assertEquals(Sort.Direction.ASC, orders.get(0).getDirection()); + assertEquals("title.keyword", orders.get(1).getProperty()); + assertEquals(Sort.Direction.ASC, orders.get(1).getDirection()); } @Test public void testTaskSortingQuery() { // default (no sort) Pageable actual = evaluateQuery("tasks: title eq 'test'").getPageable(); - assert !actual.getSort().isSorted(); + assertFalse(actual.getSort().isSorted()); // default ordering asc actual = evaluateQuery("tasks: title eq 'test' sort by id").getPageable(); - assert actual.getSort().isSorted(); + assertTrue(actual.getSort().isSorted()); List<Sort.Order> orders = actual.getSort().toList(); - assert orders.size() == 1; - assert orders.get(0).getProperty().equals("id"); - assert orders.get(0).getDirection() == Sort.Direction.ASC; + assertEquals(1, orders.size()); + assertEquals("id", orders.get(0).getProperty()); + assertEquals(Sort.Direction.ASC, orders.get(0).getDirection()); // set ordering actual = evaluateQuery("tasks: title eq 'test' sort by id desc").getPageable(); - assert actual.getSort().isSorted(); + assertTrue(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; + assertEquals(1, orders.size()); + assertEquals("id", orders.get(0).getProperty()); + assertEquals(Sort.Direction.DESC, orders.get(0).getDirection()); actual = evaluateQuery("tasks: title eq 'test' sort by transitionId desc").getPageable(); - assert actual.getSort().isSorted(); + assertTrue(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; + assertEquals(1, orders.size()); + assertEquals("transitionId", orders.get(0).getProperty()); + assertEquals(Sort.Direction.DESC, orders.get(0).getDirection()); actual = evaluateQuery("tasks: title eq 'test' sort by title asc").getPageable(); - assert actual.getSort().isSorted(); + assertTrue(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; + assertEquals(1, orders.size()); + assertEquals("title.defaultValue", orders.get(0).getProperty()); + assertEquals(Sort.Direction.ASC, orders.get(0).getDirection()); actual = evaluateQuery("tasks: title eq 'test' sort by processId asc").getPageable(); - assert actual.getSort().isSorted(); + assertTrue(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; + assertEquals(1, orders.size()); + assertEquals("processId", orders.get(0).getProperty()); + assertEquals(Sort.Direction.ASC, orders.get(0).getDirection()); actual = evaluateQuery("tasks: title eq 'test' sort by caseId asc").getPageable(); - assert actual.getSort().isSorted(); + assertTrue(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; + assertEquals(1, orders.size()); + assertEquals("caseId", orders.get(0).getProperty()); + assertEquals(Sort.Direction.ASC, orders.get(0).getDirection()); actual = evaluateQuery("tasks: title eq 'test' sort by userId asc").getPageable(); - assert actual.getSort().isSorted(); + assertTrue(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; + assertEquals(1, orders.size()); + assertEquals("userId", orders.get(0).getProperty()); + assertEquals(Sort.Direction.ASC, orders.get(0).getDirection()); actual = evaluateQuery("tasks: title eq 'test' sort by lastAssign asc").getPageable(); - assert actual.getSort().isSorted(); + assertTrue(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; + assertEquals(1, orders.size()); + assertEquals("lastAssigned", orders.get(0).getProperty()); + assertEquals(Sort.Direction.ASC, orders.get(0).getDirection()); actual = evaluateQuery("tasks: title eq 'test' sort by lastFinish desc").getPageable(); - assert actual.getSort().isSorted(); + assertTrue(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; + assertEquals(1, orders.size()); + assertEquals("lastFinished", orders.get(0).getProperty()); + assertEquals(Sort.Direction.DESC, orders.get(0).getDirection()); // complex set ordering actual = evaluateQuery("tasks: title eq 'test' sort by id asc, title desc").getPageable(); - assert actual.getSort().isSorted(); + assertTrue(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); + assertEquals(2, orders.size()); + assertEquals("id", orders.get(0).getProperty()); + assertEquals(Sort.Direction.ASC, orders.get(0).getDirection()); + assertEquals("title.defaultValue", orders.get(1).getProperty()); + assertEquals(Sort.Direction.DESC, orders.get(1).getDirection()); // complex default ordering actual = evaluateQuery("tasks: title eq 'test' sort by id asc, title").getPageable(); - assert actual.getSort().isSorted(); + assertTrue(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); + assertEquals(2, orders.size()); + assertEquals("id", orders.get(0).getProperty()); + assertEquals(Sort.Direction.ASC, orders.get(0).getDirection()); + assertEquals("title.defaultValue", orders.get(1).getProperty()); + assertEquals(Sort.Direction.ASC, orders.get(1).getDirection()); } @Test public void testUserSortingQuery() { // default (no sort) Pageable actual = evaluateQuery("users: name eq 'test'").getPageable(); - assert !actual.getSort().isSorted(); + assertFalse(actual.getSort().isSorted()); // default ordering asc actual = evaluateQuery("users: name eq 'test' sort by id").getPageable(); - assert actual.getSort().isSorted(); + assertTrue(actual.getSort().isSorted()); List<Sort.Order> orders = actual.getSort().toList(); - assert orders.size() == 1; - assert orders.get(0).getProperty().equals("id"); - assert orders.get(0).getDirection() == Sort.Direction.ASC; + assertEquals(1, orders.size()); + assertEquals("id", orders.get(0).getProperty()); + assertEquals(Sort.Direction.ASC, orders.get(0).getDirection()); // set ordering actual = evaluateQuery("users: name eq 'test' sort by id desc").getPageable(); - assert actual.getSort().isSorted(); + assertTrue(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; + assertEquals(1, orders.size()); + assertEquals("id", orders.get(0).getProperty()); + assertEquals(Sort.Direction.DESC, orders.get(0).getDirection()); actual = evaluateQuery("users: name eq 'test' sort by name desc").getPageable(); - assert actual.getSort().isSorted(); + assertTrue(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; + assertEquals(1, orders.size()); + assertEquals("name", orders.get(0).getProperty()); + assertEquals(Sort.Direction.DESC, orders.get(0).getDirection()); actual = evaluateQuery("users: name eq 'test' sort by surname asc").getPageable(); - assert actual.getSort().isSorted(); + assertTrue(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; + assertEquals(1, orders.size()); + assertEquals("surname", orders.get(0).getProperty()); + assertEquals(Sort.Direction.ASC, orders.get(0).getDirection()); actual = evaluateQuery("users: name eq 'test' sort by email asc").getPageable(); - assert actual.getSort().isSorted(); + assertTrue(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; + assertEquals(1, orders.size()); + assertEquals("email", orders.get(0).getProperty()); + assertEquals(Sort.Direction.ASC, orders.get(0).getDirection()); // complex set ordering actual = evaluateQuery("users: name eq 'test' sort by id asc, name desc").getPageable(); - assert actual.getSort().isSorted(); + assertTrue(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); + assertEquals(2, orders.size()); + assertEquals("id", orders.get(0).getProperty()); + assertEquals(Sort.Direction.ASC, orders.get(0).getDirection()); + assertEquals("name", orders.get(1).getProperty()); + assertEquals(Sort.Direction.DESC, orders.get(1).getDirection()); // complex default ordering actual = evaluateQuery("users: name eq 'test' sort by id asc, name").getPageable(); - assert actual.getSort().isSorted(); + assertTrue(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); + assertEquals(2, orders.size()); + assertEquals("id", orders.get(0).getProperty()); + assertEquals(Sort.Direction.ASC, orders.get(0).getDirection()); + assertEquals("name", orders.get(1).getProperty()); + assertEquals(Sort.Direction.ASC, orders.get(1).getDirection()); } @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'")); + assertThrows(IllegalArgumentException.class, () -> evaluateQuery("process: processId eq 'test'")); + assertThrows(IllegalArgumentException.class, () -> evaluateQuery("process: processIdentifier eq 'test'")); + assertThrows(IllegalArgumentException.class, () -> evaluateQuery("process: author eq 'test'")); + assertThrows(IllegalArgumentException.class, () -> evaluateQuery("process: places.p1.marking eq 1")); + assertThrows(IllegalArgumentException.class, () -> evaluateQuery("process: tasks.t1.state eq enabled")); + assertThrows(IllegalArgumentException.class, () -> evaluateQuery("process: tasks.t1.userId eq 'test'")); + assertThrows(IllegalArgumentException.class, () -> evaluateQuery("process: data.field1.value eq 'test'")); + assertThrows(IllegalArgumentException.class, () -> evaluateQuery("process: data.field1.options contains 'test'")); + assertThrows(IllegalArgumentException.class, () -> evaluateQuery("process: transitionId eq 'test'")); + assertThrows(IllegalArgumentException.class, () -> evaluateQuery("process: state eq enabled")); + assertThrows(IllegalArgumentException.class, () -> evaluateQuery("process: userId eq 'test'")); + assertThrows(IllegalArgumentException.class, () -> evaluateQuery("process: caseId eq 'test'")); + assertThrows(IllegalArgumentException.class, () -> evaluateQuery("process: lastAssign eq 2020-03-03")); + assertThrows(IllegalArgumentException.class, () -> evaluateQuery("process: lastFinish eq 2020-03-03")); + assertThrows(IllegalArgumentException.class, () -> evaluateQuery("process: name eq 'test'")); + assertThrows(IllegalArgumentException.class, () -> evaluateQuery("process: surname eq 'test'")); + 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'")); + assertThrows(IllegalArgumentException.class, () -> evaluateQuery("case: identifier eq 'test'")); + assertThrows(IllegalArgumentException.class, () -> evaluateQuery("case: version eq 1.1.1")); + assertThrows(IllegalArgumentException.class, () -> evaluateQuery("case: transitionId eq 1")); + assertThrows(IllegalArgumentException.class, () -> evaluateQuery("case: state eq enabled")); + assertThrows(IllegalArgumentException.class, () -> evaluateQuery("case: userId eq 'test'")); + assertThrows(IllegalArgumentException.class, () -> evaluateQuery("case: caseId eq 'test'")); + assertThrows(IllegalArgumentException.class, () -> evaluateQuery("case: lastAssign eq 2020-03-03")); + assertThrows(IllegalArgumentException.class, () -> evaluateQuery("case: lastFinish eq 2020-03-03")); + assertThrows(IllegalArgumentException.class, () -> evaluateQuery("case: name eq 'test'")); + assertThrows(IllegalArgumentException.class, () -> evaluateQuery("case: surname eq 'test'")); + 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'")); + assertThrows(IllegalArgumentException.class, () -> evaluateQuery("task: identifier eq 'test'")); + assertThrows(IllegalArgumentException.class, () -> evaluateQuery("task: version eq 1.1.1")); + assertThrows(IllegalArgumentException.class, () -> evaluateQuery("task: creationDate eq 2020-03-03")); + assertThrows(IllegalArgumentException.class, () -> evaluateQuery("task: processIdentifier eq 'test'")); + assertThrows(IllegalArgumentException.class, () -> evaluateQuery("task: places.p1.marking eq 1")); + assertThrows(IllegalArgumentException.class, () -> evaluateQuery("task: tasks.t1.state eq enabled")); + assertThrows(IllegalArgumentException.class, () -> evaluateQuery("task: tasks.t1.userId eq 'test'")); + assertThrows(IllegalArgumentException.class, () -> evaluateQuery("task: data.field1.value eq 'test'")); + assertThrows(IllegalArgumentException.class, () -> evaluateQuery("task: data.field1.options contains 'test'")); + assertThrows(IllegalArgumentException.class, () -> evaluateQuery("task: name eq 'test'")); + assertThrows(IllegalArgumentException.class, () -> evaluateQuery("task: surname eq 'test'")); + 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")); + assertThrows(IllegalArgumentException.class, () -> evaluateQuery("user: identifier eq 'test'")); + assertThrows(IllegalArgumentException.class, () -> evaluateQuery("user: version eq 1.1.1")); + assertThrows(IllegalArgumentException.class, () -> evaluateQuery("user: creationDate eq 2020-03-03")); + assertThrows(IllegalArgumentException.class, () -> evaluateQuery("user: processId eq 'test'")); + assertThrows(IllegalArgumentException.class, () -> evaluateQuery("user: processIdentifier eq 'test'")); + assertThrows(IllegalArgumentException.class, () -> evaluateQuery("user: author eq 'test'")); + assertThrows(IllegalArgumentException.class, () -> evaluateQuery("user: places.p1.marking eq 1")); + assertThrows(IllegalArgumentException.class, () -> evaluateQuery("user: tasks.t1.state eq enabled")); + assertThrows(IllegalArgumentException.class, () -> evaluateQuery("user: tasks.t1.userId eq 'test'")); + assertThrows(IllegalArgumentException.class, () -> evaluateQuery("user: data.field1.value eq 'test'")); + assertThrows(IllegalArgumentException.class, () -> evaluateQuery("user: data.field1.options contains 'test'")); + assertThrows(IllegalArgumentException.class, () -> evaluateQuery("user: transitionId eq 'test'")); + assertThrows(IllegalArgumentException.class, () -> evaluateQuery("user: state eq enabled")); + assertThrows(IllegalArgumentException.class, () -> evaluateQuery("user: userId eq 'test'")); + assertThrows(IllegalArgumentException.class, () -> evaluateQuery("user: caseId eq 'test'")); + assertThrows(IllegalArgumentException.class, () -> evaluateQuery("user: lastAssign eq 2020-03-03")); + 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'")); + assertThrows(IllegalArgumentException.class, () -> evaluateQuery("case: id contains 'test'")); + assertThrows(IllegalArgumentException.class, () -> evaluateQuery("case: id lt 'test'")); + assertThrows(IllegalArgumentException.class, () -> evaluateQuery("case: id lte 'test'")); + assertThrows(IllegalArgumentException.class, () -> evaluateQuery("case: id gt 'test'")); + assertThrows(IllegalArgumentException.class, () -> evaluateQuery("case: id gte 'test'")); // number comparison - Assertions.assertThrows(IllegalArgumentException.class, () -> evaluateQuery("case: places.p1.marking contains 1")); + assertThrows(IllegalArgumentException.class, () -> evaluateQuery("case: places.p1.marking contains 1")); // date/datetime comparison - Assertions.assertThrows(IllegalArgumentException.class, () -> evaluateQuery("case: creationDate contains 2020-03-03")); + 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")); + assertThrows(IllegalArgumentException.class, () -> evaluateQuery("case: data.field1.value contains true")); + assertThrows(IllegalArgumentException.class, () -> evaluateQuery("case: data.field1.value lt true")); + assertThrows(IllegalArgumentException.class, () -> evaluateQuery("case: data.field1.value lte true")); + assertThrows(IllegalArgumentException.class, () -> evaluateQuery("case: data.field1.value gt true")); + assertThrows(IllegalArgumentException.class, () -> evaluateQuery("case: data.field1.value gte true")); } private static void checkStringComparison(MongoDbUtils<?> mongoDbUtils, String resource, String attribute, StringPath stringPath) { @@ -1817,109 +1828,109 @@ private static void checkStringComparisonElastic(String resource, String attribu String actual = evaluateQuery(String.format("%s: %s eq 'test'", resource, attribute)).getFullElasticQuery(); String expected = String.format("%s:test", resultAttribute); - assert actual.equals(expected); + assertEquals(expected, actual); actual = evaluateQuery(String.format("%s: %s contains 'test'", resource, attribute)).getFullElasticQuery(); expected = String.format("%s:*test*", resultAttribute); - assert actual.equals(expected); + assertEquals(expected, actual); actual = evaluateQuery(String.format("%s: %s lt 'test'", resource, attribute)).getFullElasticQuery(); expected = String.format("%s:<test", resultAttribute); - assert actual.equals(expected); + assertEquals(expected, actual); actual = evaluateQuery(String.format("%s: %s lte 'test'", resource, attribute)).getFullElasticQuery(); expected = String.format("%s:<=test", resultAttribute); - assert actual.equals(expected); + assertEquals(expected, actual); actual = evaluateQuery(String.format("%s: %s gt 'test'", resource, attribute)).getFullElasticQuery(); expected = String.format("%s:>test", resultAttribute); - assert actual.equals(expected); + assertEquals(expected, actual); actual = evaluateQuery(String.format("%s: %s gte 'test'", resource, attribute)).getFullElasticQuery(); expected = String.format("%s:>=test", resultAttribute); - assert actual.equals(expected); + assertEquals(expected, actual); 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); + assertEquals(expected, actual); 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); + assertEquals(expected, actual); actual = evaluateQuery(String.format("%s: %s in ('test1' : 'test2')", resource, attribute)).getFullElasticQuery(); expected = String.format("(%s:>test1 AND %s:<test2)", resultAttribute, resultAttribute); - assert actual.equals(expected); + assertEquals(expected, actual); actual = evaluateQuery(String.format("%s: %s in ['test1' : 'test2']", resource, attribute)).getFullElasticQuery(); expected = String.format("(%s:>=test1 AND %s:<=test2)", resultAttribute, resultAttribute); - assert actual.equals(expected); + assertEquals(expected, actual); 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); + assertEquals(expected, actual); } 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); + assertEquals(expected, actual); actual = evaluateQuery(String.format("%s: %s lt 1", resource, attribute)).getFullElasticQuery(); expected = String.format("%s:<1", resultAttribute); - assert actual.equals(expected); + assertEquals(expected, actual); actual = evaluateQuery(String.format("%s: %s lte 1", resource, attribute)).getFullElasticQuery(); expected = String.format("%s:<=1", resultAttribute); - assert actual.equals(expected); + assertEquals(expected, actual); actual = evaluateQuery(String.format("%s: %s gt 1", resource, attribute)).getFullElasticQuery(); expected = String.format("%s:>1", resultAttribute); - assert actual.equals(expected); + assertEquals(expected, actual); actual = evaluateQuery(String.format("%s: %s gte 1", resource, attribute)).getFullElasticQuery(); expected = String.format("%s:>=1", resultAttribute); - assert actual.equals(expected); + assertEquals(expected, actual); 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); + assertEquals(expected, actual); 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); + assertEquals(expected, actual); 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); + assertEquals(expected, actual); 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); + assertEquals(expected, actual); 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); + assertEquals(expected, actual); } private static void checkDateComparisonElastic(String resource, String attribute, String resultAttribute) { @@ -1933,83 +1944,83 @@ private static void checkDateComparisonElastic(String resource, String attribute 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); + assertEquals(expected, actual); 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); + assertEquals(expected, actual); 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); + assertEquals(expected, actual); 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); + assertEquals(expected, actual); 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); + assertEquals(expected, actual); 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); + assertEquals(expected, actual); 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); + assertEquals(expected, actual); 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); + assertEquals(expected, actual); 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); + assertEquals(expected, actual); 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); + assertEquals(expected, actual); 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); + assertEquals(expected, actual); 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); + assertEquals(expected, actual); 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); + assertEquals(expected, actual); 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); + assertEquals(expected, actual); 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); + assertEquals(expected, actual); } private static void compareMongoQueries(MongoDbUtils<?> mongoDbUtils, Predicate actual, Predicate expected) { Document actualDocument = mongoDbUtils.convertPredicateToDocument(actual); Document expectedDocument = mongoDbUtils.convertPredicateToDocument(expected); - assert actualDocument.equals(expectedDocument); + assertEquals(expectedDocument, actualDocument); } } From 7916274a8cece33d88fb7b5e43cb34d0800907dc Mon Sep 17 00:00:00 2001 From: chvostek <chvostek@netgrif.com> Date: Mon, 8 Jun 2026 20:40:52 +0200 Subject: [PATCH 15/17] [NAE-2443] PFQL support - after merge fix --- .../service/LegacyTaskSearchService.java | 26 +++++-------------- 1 file changed, 6 insertions(+), 20 deletions(-) diff --git a/src/main/java/com/netgrif/application/engine/workflow/service/LegacyTaskSearchService.java b/src/main/java/com/netgrif/application/engine/workflow/service/LegacyTaskSearchService.java index cdbeb4a64af..7ce966aac50 100644 --- a/src/main/java/com/netgrif/application/engine/workflow/service/LegacyTaskSearchService.java +++ b/src/main/java/com/netgrif/application/engine/workflow/service/LegacyTaskSearchService.java @@ -33,7 +33,7 @@ public Predicate buildQuery(List<TaskSearchRequest> requests, LoggedUser user, L return null; } else if (!isIntersection) { singleQueries = singleQueries.stream().filter(Objects::nonNull).collect(Collectors.toList()); - if (singleQueries.size() == 0) { + if (singleQueries.isEmpty()) { // all queries result in an empty set => the entire result is an empty set return null; } @@ -41,6 +41,10 @@ public Predicate buildQuery(List<TaskSearchRequest> requests, LoggedUser user, L BooleanBuilder builder = constructPredicateTree(singleQueries, isIntersection ? BooleanBuilder::and : BooleanBuilder::or); + return builder.and(buildPermissionConstraints(loggedOrImpersonated)); + } + + public BooleanBuilder buildPermissionConstraints(LoggedUser loggedOrImpersonated) { // (Rp!=0 & Rn = 0) BooleanBuilder constraints = new BooleanBuilder(buildViewRoleQueryConstraint(loggedOrImpersonated)) .andNot(buildNegativeViewRoleQueryConstraint(loggedOrImpersonated)); @@ -51,25 +55,7 @@ public Predicate buildQuery(List<TaskSearchRequest> requests, LoggedUser user, L // (((Rp!=0 & Rn = 0) or Up!=0) & Un=0) == 1 constraints.andNot(buildNegativeViewUsersQueryConstraint(loggedOrImpersonated)); - return builder.and(constraints); - } - - public BooleanBuilder buildPermissionConstraints(LoggedUser loggedOrImpersonated) { - BooleanBuilder permissionConstraints = new BooleanBuilder(buildViewRoleQueryConstraint(loggedOrImpersonated)); - permissionConstraints.andNot(buildNegativeViewRoleQueryConstraint(loggedOrImpersonated)); - permissionConstraints.or(buildViewUserQueryConstraint(loggedOrImpersonated)); - permissionConstraints.andNot(buildNegativeViewUsersQueryConstraint(loggedOrImpersonated)); - return permissionConstraints; - } - - protected Predicate buildRolesQueryConstraint(LoggedUser user) { - List<Predicate> roleConstraints = user.getProcessRoles().stream().map(this::roleQuery).collect(Collectors.toList()); - return constructPredicateTree(roleConstraints, BooleanBuilder::or); - } - - protected Predicate buildUserRefQueryConstraint(LoggedUser user) { - Predicate userRefConstraints = userRefQuery(user.getId()); - return constructPredicateTree(Collections.singletonList(userRefConstraints), BooleanBuilder::or); + return constraints; } protected Predicate buildViewRoleQueryConstraint(LoggedUser user) { From a14a42dcdfb4524e1163abe5683ad0423acd6458 Mon Sep 17 00:00:00 2001 From: chvostek <chvostek@netgrif.com> Date: Tue, 9 Jun 2026 08:11:58 +0200 Subject: [PATCH 16/17] [NAE-2443] PFQL support - fix tests - add todo to LegacyCaseSearchService --- .../engine/workflow/service/LegacyCaseSearchService.java | 2 ++ .../application/engine/elastic/ReindexTest.groovy | 4 ++-- .../java/com/netgrif/application/engine/MockService.java | 9 ++++++++- src/test/resources/all_data.xml | 1 + src/test/resources/all_data_pdf.xml | 1 + src/test/resources/ipc_data.xml | 1 + src/test/resources/ipc_set_data.xml | 1 + src/test/resources/pdf_test_3.xml | 1 + src/test/resources/petriNets/impersonation_test.xml | 6 ++++++ src/test/resources/variable_arc_test.xml | 1 + 10 files changed, 24 insertions(+), 3 deletions(-) diff --git a/src/main/java/com/netgrif/application/engine/workflow/service/LegacyCaseSearchService.java b/src/main/java/com/netgrif/application/engine/workflow/service/LegacyCaseSearchService.java index 184fd2e91ca..993fee2d6e8 100644 --- a/src/main/java/com/netgrif/application/engine/workflow/service/LegacyCaseSearchService.java +++ b/src/main/java/com/netgrif/application/engine/workflow/service/LegacyCaseSearchService.java @@ -89,11 +89,13 @@ public Predicate buildQuery(Map<String, Object> requestQuery, LoggedUser user, L return null; } } + // todo: move latest logic to method buildPermissionConstraints builder.and(buildPermissionConstraints(loggedOrImpersonated)); return builder; } public BooleanBuilder buildPermissionConstraints(LoggedUser loggedOrImpersonated) { + // todo: update with latest logic, that Samo has introduced BooleanBuilder permissionConstraints = new BooleanBuilder(buildViewRoleQueryConstraint(loggedOrImpersonated)); permissionConstraints.andNot(buildNegativeViewRoleQueryConstraint(loggedOrImpersonated)); permissionConstraints.or(buildViewUserQueryConstraint(loggedOrImpersonated)); diff --git a/src/test/groovy/com/netgrif/application/engine/elastic/ReindexTest.groovy b/src/test/groovy/com/netgrif/application/engine/elastic/ReindexTest.groovy index db93093eb26..851f27d19e6 100644 --- a/src/test/groovy/com/netgrif/application/engine/elastic/ReindexTest.groovy +++ b/src/test/groovy/com/netgrif/application/engine/elastic/ReindexTest.groovy @@ -35,13 +35,13 @@ class ReindexTest { private SuperCreator superCreator @Autowired - protected IElasticCaseService elasticCaseService + private IElasticCaseService elasticCaseService @Autowired private ReindexingTask reindexingTask @Autowired - TestHelper testHelper + private TestHelper testHelper @BeforeEach void before() { diff --git a/src/test/java/com/netgrif/application/engine/MockService.java b/src/test/java/com/netgrif/application/engine/MockService.java index 524cb597c38..c20b33fd769 100644 --- a/src/test/java/com/netgrif/application/engine/MockService.java +++ b/src/test/java/com/netgrif/application/engine/MockService.java @@ -3,12 +3,14 @@ import com.netgrif.application.engine.auth.domain.Authority; import com.netgrif.application.engine.auth.domain.LoggedUser; import com.netgrif.application.engine.auth.service.interfaces.IAuthorityService; +import com.netgrif.application.engine.petrinet.service.interfaces.IProcessRoleService; import org.bson.types.ObjectId; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.context.annotation.Profile; import org.springframework.stereotype.Component; import java.util.Collections; +import java.util.Set; @Component @Profile("test") @@ -17,8 +19,13 @@ public class MockService { @Autowired private IAuthorityService authorityService; + @Autowired + private IProcessRoleService processRoleService; + public LoggedUser mockLoggedUser() { Authority authorityUser = authorityService.getOrCreate(Authority.user); - return new LoggedUser(new ObjectId().toString(), "super@netgrif.com", "password", Collections.singleton(authorityUser)); + LoggedUser loggedUser = new LoggedUser(new ObjectId().toString(), "super@netgrif.com", "password", Collections.singleton(authorityUser)); + loggedUser.setProcessRoles(Set.of(processRoleService.defaultRole().getStringId())); + return loggedUser; } } diff --git a/src/test/resources/all_data.xml b/src/test/resources/all_data.xml index b1038511029..95ed6a75fd5 100644 --- a/src/test/resources/all_data.xml +++ b/src/test/resources/all_data.xml @@ -3,6 +3,7 @@ <id>all_data</id> <title>All Data ALL + true process_role diff --git a/src/test/resources/all_data_pdf.xml b/src/test/resources/all_data_pdf.xml index 408e03836b3..0b52bb79dd7 100644 --- a/src/test/resources/all_data_pdf.xml +++ b/src/test/resources/all_data_pdf.xml @@ -3,6 +3,7 @@ all_data All Data ALL + true process_role diff --git a/src/test/resources/ipc_data.xml b/src/test/resources/ipc_data.xml index 4a99b3de73c..7916e11cf1a 100644 --- a/src/test/resources/ipc_data.xml +++ b/src/test/resources/ipc_data.xml @@ -4,6 +4,7 @@ test TST Test + true data_text diff --git a/src/test/resources/ipc_set_data.xml b/src/test/resources/ipc_set_data.xml index 1e94beee85c..f9134bb005e 100644 --- a/src/test/resources/ipc_set_data.xml +++ b/src/test/resources/ipc_set_data.xml @@ -4,6 +4,7 @@ test TST Test + true data_text diff --git a/src/test/resources/pdf_test_3.xml b/src/test/resources/pdf_test_3.xml index 4755336cbac..af32844348b 100644 --- a/src/test/resources/pdf_test_3.xml +++ b/src/test/resources/pdf_test_3.xml @@ -6,6 +6,7 @@ PER Personal Information contacts + true Surname Question diff --git a/src/test/resources/petriNets/impersonation_test.xml b/src/test/resources/petriNets/impersonation_test.xml index 5ed2917f12a..3cf7c8a8747 100644 --- a/src/test/resources/petriNets/impersonation_test.xml +++ b/src/test/resources/petriNets/impersonation_test.xml @@ -14,6 +14,12 @@ true + + default + + true + + test_role TEST diff --git a/src/test/resources/variable_arc_test.xml b/src/test/resources/variable_arc_test.xml index 199883e5db9..dcbc0bb503d 100644 --- a/src/test/resources/variable_arc_test.xml +++ b/src/test/resources/variable_arc_test.xml @@ -3,6 +3,7 @@ variable_arc_test.xml TST Test + true From f27a0727e99431522bafeaeced554d0d08a50689 Mon Sep 17 00:00:00 2001 From: chvostek Date: Tue, 9 Jun 2026 09:59:03 +0200 Subject: [PATCH 17/17] [NAE-2443] PFQL support - resolve PR comments --- .../logic/action/ActionDelegate.groovy | 21 +++++----------- .../petrinet/service/PetriNetService.java | 20 +++------------- .../engine/pfql/domain/antlr4/QueryLang.g4 | 24 +++++++++++++++---- .../pfql/service/QueryLangErrorListener.java | 3 +++ .../pfql/service/QueryLangEvaluator.java | 12 +++++++++- .../service/QueryLangExplainEvaluator.java | 3 ++- .../engine/pfql/service/SearchService.java | 9 +++++++ .../pfql/service/utils/SearchUtils.java | 18 ++++++++++---- .../engine/workflow/service/TaskService.java | 1 - .../engine/pfql/QueryLangTest.java | 3 +++ .../engine/pfql/utils/SearchTestUtils.java | 4 +++- 11 files changed, 72 insertions(+), 46 deletions(-) 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 d6a773bc2ec..2179ff7641c 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 @@ -48,22 +48,15 @@ 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.IProcessRoleService import com.netgrif.application.engine.petrinet.service.interfaces.IUriService +import com.netgrif.application.engine.pfql.service.IResourceSearchService import com.netgrif.application.engine.pfql.service.ISearchService -import com.netgrif.application.engine.pfql.service.caseresource.CaseSearchService -import com.netgrif.application.engine.pfql.service.processresource.ProcessSearchService -import com.netgrif.application.engine.pfql.service.taskresource.TaskSearchService -import com.netgrif.application.engine.pfql.service.userresource.UserSearchService import com.netgrif.application.engine.pfql.service.utils.SearchUtils import com.netgrif.application.engine.rules.domain.RuleRepository import com.netgrif.application.engine.startup.DefaultFiltersRunner import com.netgrif.application.engine.startup.FilterRunner import com.netgrif.application.engine.startup.ImportHelper import com.netgrif.application.engine.utils.FullPageRequest -import com.netgrif.application.engine.workflow.domain.Case -import com.netgrif.application.engine.workflow.domain.DataField -import com.netgrif.application.engine.workflow.domain.QCase -import com.netgrif.application.engine.workflow.domain.QTask -import com.netgrif.application.engine.workflow.domain.Task +import com.netgrif.application.engine.workflow.domain.* import com.netgrif.application.engine.workflow.domain.eventoutcomes.EventOutcome import com.netgrif.application.engine.workflow.domain.eventoutcomes.caseoutcomes.CreateCaseEventOutcome import com.netgrif.application.engine.workflow.domain.eventoutcomes.dataoutcomes.GetDataEventOutcome @@ -94,7 +87,6 @@ import org.springframework.data.domain.Pageable import java.time.ZoneId import java.util.stream.Collectors - /** * ActionDelegate class contains Actions API methods. */ @@ -220,16 +212,16 @@ class ActionDelegate { ISearchService searchService @Autowired - CaseSearchService caseSearchService + IResourceSearchService caseSearchService @Autowired - TaskSearchService taskSearchService + IResourceSearchService taskSearchService @Autowired - ProcessSearchService processSearchService + IResourceSearchService processSearchService @Autowired - UserSearchService userSearchService + IResourceSearchService userSearchService FrontendActionOutcome Frontend @@ -3265,7 +3257,6 @@ class ActionDelegate { * @return list of matching cases */ List searchCases(String query) { - query = SearchUtils.ensureStartsWithCases(query) return pagedSearchCases(query).content } diff --git a/src/main/java/com/netgrif/application/engine/petrinet/service/PetriNetService.java b/src/main/java/com/netgrif/application/engine/petrinet/service/PetriNetService.java index 1c9aa2b5868..462360fbb33 100644 --- a/src/main/java/com/netgrif/application/engine/petrinet/service/PetriNetService.java +++ b/src/main/java/com/netgrif/application/engine/petrinet/service/PetriNetService.java @@ -530,9 +530,7 @@ public Page search(Predicate predicate, Pageable pageable) { if (pageable == null) { pageable = Pageable.unpaged(); } - Predicate permissionConstraint = getProcessRolesPredicate(userService.getLoggedOrSystem().transformToLoggedUser()); - Predicate finalPredicate = ExpressionUtils.and(predicate, permissionConstraint); - return repository.findAll(finalPredicate, pageable); + return repository.findAll(predicate, pageable); } @Override @@ -549,9 +547,7 @@ public long count(Predicate predicate) { if (predicate == null) { return 0; } - Predicate permissionConstraint = getProcessRolesPredicate(userService.getLoggedOrSystem().transformToLoggedUser()); - Predicate finalPredicate = ExpressionUtils.and(predicate, permissionConstraint); - return repository.count(finalPredicate); + return repository.count(predicate); } @Override @@ -559,9 +555,7 @@ public boolean exists(Predicate predicate) { if (predicate == null) { return false; } - Predicate permissionConstraint = getProcessRolesPredicate(userService.getLoggedOrSystem().transformToLoggedUser()); - Predicate finalPredicate = ExpressionUtils.and(predicate, permissionConstraint); - return repository.exists(finalPredicate); + return repository.exists(predicate); } private void addValueCriteria(Query query, Query queryTotal, Criteria criteria) { @@ -605,14 +599,6 @@ private Criteria getProcessRolesCriteria(LoggedUser user) { .map(role -> Criteria.where("permissions." + role).exists(true)).toArray(Criteria[]::new)); } - private Predicate getProcessRolesPredicate(LoggedUser user) { - BooleanBuilder result = new BooleanBuilder(); - user.getProcessRoles().forEach(role -> { - result.or(QPetriNet.petriNet.permissions.containsKey(role)); - }); - return result; - } - @Override public void runActions(List actions, PetriNet petriNet) { log.info("Running actions of net [" + petriNet.getStringId() + "]"); diff --git a/src/main/java/com/netgrif/application/engine/pfql/domain/antlr4/QueryLang.g4 b/src/main/java/com/netgrif/application/engine/pfql/domain/antlr4/QueryLang.g4 index b2fba1623e2..5cf4fa56f64 100644 --- a/src/main/java/com/netgrif/application/engine/pfql/domain/antlr4/QueryLang.g4 +++ b/src/main/java/com/netgrif/application/engine/pfql/domain/antlr4/QueryLang.g4 @@ -241,11 +241,25 @@ 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 ; +dataValue: DATA '.' fieldId=javaId '.' VALUE ; +dataOptions: DATA '.' fieldId=javaId '.' OPTIONS ; +places: PLACES '.' placeId=javaId '.' MARKING ; +tasksState: TASKS '.' taskId=javaId '.' STATE ; +tasksUserId: TASKS '.' taskId=javaId '.' USER_ID ; + +// identifier that also accepts reserved words as plain identifiers +javaId: JAVA_ID + | ID | TITLE | IDENTIFIER | VERSION | CREATION_DATE + | PROCESS_ID | PROCESS_IDENTIFIER | AUTHOR | PLACES + | TRANSITION_ID | STATE | USER_ID | CASE_ID + | LAST_ASSIGN | LAST_FINISH + | NAME | SURNAME | EMAIL + | DATA | VALUE | OPTIONS | MARKING + | ENABLED | DISABLED + | PAGE | SIZE | ASC | DESC + | CASE | CASES | TASK | TASKS + | USER | USERS | PROCESS | PROCESSES + ; // operators AND: A N D | '&' ; diff --git a/src/main/java/com/netgrif/application/engine/pfql/service/QueryLangErrorListener.java b/src/main/java/com/netgrif/application/engine/pfql/service/QueryLangErrorListener.java index bfb03e94102..34f9455511c 100644 --- a/src/main/java/com/netgrif/application/engine/pfql/service/QueryLangErrorListener.java +++ b/src/main/java/com/netgrif/application/engine/pfql/service/QueryLangErrorListener.java @@ -27,6 +27,9 @@ protected String underlineError(Recognizer recognizer, Token offendingToke CommonTokenStream tokens = (CommonTokenStream) recognizer.getInputStream(); String input = tokens.getTokenSource().getInputStream().toString(); String[] lines = input.split("\n"); + if (line < 1 || line > lines.length) { + return underlineErrorMsg; + } String errorLine = lines[line - 1]; underlineErrorMsg += errorLine + "\n"; underlineErrorMsg += " ".repeat(charPositionInLine) + "^".repeat(stop - start + 1) + "\n"; diff --git a/src/main/java/com/netgrif/application/engine/pfql/service/QueryLangEvaluator.java b/src/main/java/com/netgrif/application/engine/pfql/service/QueryLangEvaluator.java index feba51bf24e..f7832efaa5a 100644 --- a/src/main/java/com/netgrif/application/engine/pfql/service/QueryLangEvaluator.java +++ b/src/main/java/com/netgrif/application/engine/pfql/service/QueryLangEvaluator.java @@ -807,6 +807,7 @@ public void exitTransitionIdRange(QueryLangParser.TransitionIdRangeContext ctx) @Override public void exitStateComparison(QueryLangParser.StateComparisonContext ctx) { + // todo implement task states // switch (ctx.state.getType()) { // case QueryLangParser.ENABLED: // setMongoQuery(ctx, QTask.task.state.eq(State.ENABLED)); @@ -861,6 +862,7 @@ public void exitCaseIdList(QueryLangParser.CaseIdListContext ctx) { @Override public void exitLaDateBasic(QueryLangParser.LaDateBasicContext ctx) { + // todo implement lastAssigned // DateTimePath dateTimePath = QTask.task.lastAssigned; // Token op = ctx.dateComparison().op; // boolean not = ctx.dateComparison().NOT() != null; @@ -871,6 +873,7 @@ public void exitLaDateBasic(QueryLangParser.LaDateBasicContext ctx) { @Override public void exitLaDateTimeBasic(QueryLangParser.LaDateTimeBasicContext ctx) { + // todo implement lastAssigned // DateTimePath dateTimePath = QTask.task.lastAssigned; // Token op = ctx.dateTimeComparison().op; // boolean not = ctx.dateTimeComparison().NOT() != null; @@ -881,6 +884,7 @@ public void exitLaDateTimeBasic(QueryLangParser.LaDateTimeBasicContext ctx) { @Override public void exitLaDateList(QueryLangParser.LaDateListContext ctx) { + // todo implement lastAssigned // 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() ; @@ -891,6 +895,7 @@ public void exitLaDateList(QueryLangParser.LaDateListContext ctx) { @Override public void exitLaDateRange(QueryLangParser.LaDateRangeContext ctx) { + // todo implement lastAssigned // DateTimePath dateTimePath = QTask.task.lastAssigned; // boolean not = ctx.inRangeDateComparison().NOT() != null; // boolean leftEndpointOpen; @@ -914,6 +919,7 @@ public void exitLaDateRange(QueryLangParser.LaDateRangeContext ctx) { @Override public void exitLfDateBasic(QueryLangParser.LfDateBasicContext ctx) { + // todo implement lastFinished // DateTimePath dateTimePath = QTask.task.lastFinished; // Token op = ctx.dateComparison().op; // boolean not = ctx.dateComparison().NOT() != null; @@ -924,6 +930,7 @@ public void exitLfDateBasic(QueryLangParser.LfDateBasicContext ctx) { @Override public void exitLfDateTimeBasic(QueryLangParser.LfDateTimeBasicContext ctx) { + // todo implement lastFinished // DateTimePath dateTimePath = QTask.task.lastFinished; // Token op = ctx.dateTimeComparison().op; // boolean not = ctx.dateTimeComparison().NOT() != null; @@ -934,6 +941,7 @@ public void exitLfDateTimeBasic(QueryLangParser.LfDateTimeBasicContext ctx) { @Override public void exitLfDateList(QueryLangParser.LfDateListContext ctx) { + // todo implement lastFinished // 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() ; @@ -944,6 +952,7 @@ public void exitLfDateList(QueryLangParser.LfDateListContext ctx) { @Override public void exitLfDateRange(QueryLangParser.LfDateRangeContext ctx) { + // todo implement lastFinished // DateTimePath dateTimePath = QTask.task.lastFinished; // boolean not = ctx.inRangeDateComparison().NOT() != null; // boolean leftEndpointOpen; @@ -1315,6 +1324,7 @@ public void exitPlacesRange(QueryLangParser.PlacesRangeContext ctx) { @Override public void exitTasksStateComparison(QueryLangParser.TasksStateComparisonContext ctx) { + // todo implement task states // String taskId = ctx.tasksState().taskId.getText(); // Token op = ctx.op; // checkOp(ComparisonType.STRING, op); @@ -1367,7 +1377,7 @@ public void exitCaseSorting(QueryLangParser.CaseSortingContext ctx) { 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 + // todo: sorting by data value, options if (attrOrd.caseAttribute().places() != null) { prop = "places." + attrOrd.caseAttribute().places().placeId.getText() + ".marking"; } else if (attrOrd.caseAttribute().tasksState() != null) { diff --git a/src/main/java/com/netgrif/application/engine/pfql/service/QueryLangExplainEvaluator.java b/src/main/java/com/netgrif/application/engine/pfql/service/QueryLangExplainEvaluator.java index 18d07849988..833527cd49a 100644 --- a/src/main/java/com/netgrif/application/engine/pfql/service/QueryLangExplainEvaluator.java +++ b/src/main/java/com/netgrif/application/engine/pfql/service/QueryLangExplainEvaluator.java @@ -447,7 +447,7 @@ public void exitCaseSorting(QueryLangParser.CaseSortingContext ctx) { 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 + // todo: sorting by data value, options if (attrOrd.caseAttribute().places() != null) { prop = "places." + attrOrd.caseAttribute().places().placeId.getText() + ".marking"; } else if (attrOrd.caseAttribute().tasksState() != null) { @@ -463,6 +463,7 @@ public void exitCaseSorting(QueryLangParser.CaseSortingContext ctx) { if (prop == null) { sortOrders.add("Invalid attribute: " + attrOrd.caseAttribute().getText()); + return; } sortOrders.add("attribute: " + prop + ", ordering: " + dir); }); diff --git a/src/main/java/com/netgrif/application/engine/pfql/service/SearchService.java b/src/main/java/com/netgrif/application/engine/pfql/service/SearchService.java index ec824677864..f9a27017013 100644 --- a/src/main/java/com/netgrif/application/engine/pfql/service/SearchService.java +++ b/src/main/java/com/netgrif/application/engine/pfql/service/SearchService.java @@ -52,6 +52,9 @@ public Object search(String input) { QueryLangEvaluator evaluator = evaluateQuery(input); log.trace("Evaluated query type: {}, multiple: {}", evaluator.getResourceType(), evaluator.getMultiple()); IResourceSearchService service = this.serviceRegistry.get(evaluator.getResourceType()); + if (service == null) { + throw new IllegalStateException("No PFQL search service registered for query type: " + evaluator.getResourceType()); + } Object result = evaluator.getMultiple() ? service.searchAll(evaluator) : service.searchOne(evaluator); log.debug("Search completed, returning {} result", evaluator.getMultiple() ? "multiple" : "single"); return result; @@ -69,6 +72,9 @@ public long count(String input) { QueryLangEvaluator evaluator = evaluateQuery(input); log.trace("Evaluated query type for count: {}", evaluator.getResourceType()); IResourceSearchService service = this.serviceRegistry.get(evaluator.getResourceType()); + if (service == null) { + throw new IllegalStateException("No PFQL search service registered for query type: " + evaluator.getResourceType()); + } long count = service.count(evaluator); log.debug("Count completed, result: {}", count); return count; @@ -86,6 +92,9 @@ public boolean exists(String input) { QueryLangEvaluator evaluator = evaluateQuery(input); log.trace("Evaluated query type for exists: {}", evaluator.getResourceType()); IResourceSearchService service = this.serviceRegistry.get(evaluator.getResourceType()); + if (service == null) { + throw new IllegalStateException("No PFQL search service registered for query type: " + evaluator.getResourceType()); + } boolean exists = service.exists(evaluator); log.debug("Existence check completed, result: {}", exists); return exists; diff --git a/src/main/java/com/netgrif/application/engine/pfql/service/utils/SearchUtils.java b/src/main/java/com/netgrif/application/engine/pfql/service/utils/SearchUtils.java index 7eac4042e6d..6aba1770f68 100644 --- a/src/main/java/com/netgrif/application/engine/pfql/service/utils/SearchUtils.java +++ b/src/main/java/com/netgrif/application/engine/pfql/service/utils/SearchUtils.java @@ -126,7 +126,7 @@ public static LocalDateTime toDateTime(String dateTimeString) { try { return LocalDate.parse(dateTimeString, DateTimeFormatter.ISO_LOCAL_DATE).atTime(12, 0, 0); } catch (DateTimeParseException e) { - throw new IllegalArgumentException("Invalid date/datetime format"); + throw new IllegalArgumentException("Invalid date/datetime format", e); } } } @@ -138,7 +138,7 @@ public static LocalDate toDate(String dateString) { try { return LocalDateTime.parse(dateString, DateTimeFormatter.ISO_LOCAL_DATE_TIME).toLocalDate(); } catch (DateTimeParseException e) { - throw new IllegalArgumentException("Invalid date/datetime format"); + throw new IllegalArgumentException("Invalid date/datetime format", e); } } } @@ -280,9 +280,17 @@ public static Predicate buildStringPredicateInRange(StringPath stringPath, Strin 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]); + if (versionNumber.length != 3) { + throw new IllegalArgumentException("Version must be in format major.minor.patch: " + versionString); + } + long major, minor, patch; + try { + major = Long.parseLong(versionNumber[0]); + minor = Long.parseLong(versionNumber[1]); + patch = Long.parseLong(versionNumber[2]); + } catch (NumberFormatException e) { + throw new IllegalArgumentException("Invalid version number format: " + versionString, e); + } QVersion qVersion = QPetriNet.petriNet.version; diff --git a/src/main/java/com/netgrif/application/engine/workflow/service/TaskService.java b/src/main/java/com/netgrif/application/engine/workflow/service/TaskService.java index 2ccec2e6d1a..2e455c3e0c6 100644 --- a/src/main/java/com/netgrif/application/engine/workflow/service/TaskService.java +++ b/src/main/java/com/netgrif/application/engine/workflow/service/TaskService.java @@ -771,7 +771,6 @@ public Page search(com.querydsl.core.types.Predicate predicate, Pageable p public Task searchOne(com.querydsl.core.types.Predicate predicate) { Page tasks = search(predicate, PageRequest.of(0, 1)); if (tasks.getTotalElements() > 0) { - loadUsers(tasks); return tasks.getContent().get(0); } return null; diff --git a/src/test/java/com/netgrif/application/engine/pfql/QueryLangTest.java b/src/test/java/com/netgrif/application/engine/pfql/QueryLangTest.java index dfe7f754c25..92419009f07 100644 --- a/src/test/java/com/netgrif/application/engine/pfql/QueryLangTest.java +++ b/src/test/java/com/netgrif/application/engine/pfql/QueryLangTest.java @@ -348,6 +348,9 @@ public void testSimpleMongodbCaseQuery() { actual = evaluateQuery("case: data.field1.value eq 'test'").getFullMongoQuery(); assertNull(actual); + actual = evaluateQuery("case: data.name.value eq 'test'").getFullMongoQuery(); // name is a reserved keyword + assertNull(actual); + actual = evaluateQuery("case: data.field1.value contains 'test'").getFullMongoQuery(); assertNull(actual); diff --git a/src/test/java/com/netgrif/application/engine/pfql/utils/SearchTestUtils.java b/src/test/java/com/netgrif/application/engine/pfql/utils/SearchTestUtils.java index 8664c8a8d25..b4ed91b129e 100644 --- a/src/test/java/com/netgrif/application/engine/pfql/utils/SearchTestUtils.java +++ b/src/test/java/com/netgrif/application/engine/pfql/utils/SearchTestUtils.java @@ -40,7 +40,9 @@ public static void compareById(List actual, List expected, Function void compareByIdInOrder(List actual, List expected, Function getId) {