+ * The query must start with the resource keyword {@code users} (plural). This is a convenience
+ * method returning only the content of {@link #pagedSearchUsers(String)}.
+ *
+ * Example:
+ * searchUsers(String query) {
+ query = SearchUtils.ensureStartsWithUsers(query)
+ return pagedSearchUsers(query).content
+ }
+
+ /**
+ * Counts the number of {@link IUser} instances matching the given query.
+ *
+ * The query must start with the resource keyword {@code users} (plural).
+ *
+ * Example:
+ *
+ * countUsers("users: email contains '@company.com'")
+ * countUsers("users: name eq 'John'")
+ * countUsers("name eq 'John'")
+ *
+ *
+ * @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.
+ *
+ * The query must start with the resource keyword {@code users} (plural).
+ *
+ * Example:
+ *
+ * existsUser("users: email eq 'user@mail.com'")
+ * existsUser("users: name eq 'John' and surname eq 'Doe'")
+ * existsUser("name eq 'John' and surname eq 'Doe'")
+ *
+ *
+ * @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.
+ *
+ * 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.
+ *
+ * Example:
+ *
+ * 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'")
+ *
+ *
+ * @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>) {
+ return result.content
+ }
+ return result
+ }
+
+ /**
+ * Generic count that resolves the resource type from the query itself and counts matching instances.
+ *
+ * The query must start with one of the resource keywords: {@code processes}, {@code cases},
+ * {@code tasks} or {@code users} (plural form).
+ *
+ * Example:
+ *
+ * count("cases: processIdentifier eq 'query_test' and data.boolean_0.value == true")
+ * count("users: email contains '@company.com'")
+ *
+ *
+ * @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.
+ *
+ * The query must start with one of the resource keywords: {@code processes}, {@code cases},
+ * {@code tasks} or {@code users} (plural form).
+ *
+ * Example:
+ *
+ * exists("cases: processIdentifier eq 'query_test'")
+ * exists("tasks: transitionId eq 't1' and userId eq 'user1'")
+ *
+ *
+ * @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)
+ }
}
\ No newline at end of file
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/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/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..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
@@ -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,9 @@
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;
import org.bson.Document;
@@ -522,6 +522,42 @@ 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 (predicate == null) {
+ return Page.empty();
+ }
+ if (pageable == null) {
+ pageable = Pageable.unpaged();
+ }
+ return repository.findAll(predicate, 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 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);
+ }
+
private void addValueCriteria(Query query, Query queryTotal, Criteria criteria) {
query.addCriteria(criteria);
queryTotal.addCriteria(criteria);
@@ -531,7 +567,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 + "]");
}
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..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
@@ -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,14 @@ static DataFieldReference transformToReference(PetriNet net, Transition transiti
Page search(PetriNetSearch criteria, LoggedUser user, Pageable pageable, Locale locale);
+ Page search(Predicate predicate, Pageable pageable);
+
+ PetriNet searchOne(Predicate predicate);
+
+ 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/domain/antlr4/QueryLang.g4 b/src/main/java/com/netgrif/application/engine/pfql/domain/antlr4/QueryLang.g4
new file mode 100644
index 00000000000..5cf4fa56f64
--- /dev/null
+++ b/src/main/java/com/netgrif/application/engine/pfql/domain/antlr4/QueryLang.g4
@@ -0,0 +1,375 @@
+// todo 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 | 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 ;
+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=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 | '&' ;
+OR: O R | '|' ;
+NOT: N O T | '!' ;
+EQ: E Q | '==' ;
+NEQ: N E Q | '!=' ;
+LT: L T | '<' ;
+GT: G T | '>' ;
+LTE: L T E | '<=' ;
+GTE: G T E | '>=' ;
+CONTAINS: C O N T A I N S | '~';
+IN: I N ;
+
+// resurces
+CASE: C A S E ;
+CASES: C A S E S ;
+TASK: T A S K ;
+TASKS: T A S K S ;
+USER: U S E R ;
+USERS: U S E R S ;
+PROCESS: P R O C E S S ;
+PROCESSES: P R O C E S S E S ;
+
+// attributes
+ID: I D ;
+TITLE: T I T L E ;
+IDENTIFIER: I D E N T I F I E R ;
+VERSION: V E R S I O N ;
+CREATION_DATE: C R E A T I O N D A T E ;
+PROCESS_ID: P R O C E S S I D ;
+PROCESS_IDENTIFIER: P R O C E S S I D E N T I F I E R ;
+AUTHOR: A U T H O R ;
+PLACES: P L A C E S ;
+TRANSITION_ID: T R A N S I T I O N I D ;
+STATE: S T A T E ;
+USER_ID: U S E R I D ;
+CASE_ID: C A S E I D ;
+LAST_ASSIGN: L A S T A S S I G N ;
+LAST_FINISH: L A S T F I N I S H ;
+NAME: N A M E ;
+SURNAME: S U R N A M E ;
+EMAIL: E M A I L ;
+
+DATA: D A T A ;
+VALUE: V A L U E ;
+OPTIONS: O P T I O N S ;
+MARKING: M A R K I N G ;
+ENABLED: E N A B L E D ;
+DISABLED: D I S A B L E D ;
+
+// paging
+PAGE: P A G E ;
+SIZE: S I Z E ;
+
+// sorting
+SORT_BY: S O R T SPACE B Y ;
+ASC: A S C ;
+DESC: D E S C ;
+
+// basic types
+stringList: '(' SPACE? (STRING SPACE? (',' SPACE? STRING SPACE? )* )? SPACE? ')' ;
+intList: '(' SPACE? (INT SPACE? (',' SPACE? INT SPACE? )* )? SPACE? ')' ;
+doubleList: '(' SPACE? (DOUBLE SPACE? (',' SPACE? DOUBLE SPACE? )* )? SPACE? ')' ;
+dateList: '(' SPACE? (DATE SPACE? (',' SPACE? DATE SPACE? )* )? SPACE? ')' ;
+dateTimeList: '(' SPACE? (DATETIME SPACE? (',' SPACE? DATETIME SPACE? )* )? SPACE? ')' ;
+versionList: '(' SPACE? (VERSION_NUMBER SPACE? (',' SPACE? VERSION_NUMBER SPACE? )* )? SPACE? ')' ;
+stringRange: leftEndpoint=('(' | '[') SPACE? STRING SPACE? ':' SPACE? STRING SPACE? rightEndpoint=(')' | ']') ;
+intRange: leftEndpoint=('(' | '[') SPACE? INT SPACE? ':' SPACE? INT SPACE? rightEndpoint=(')' | ']') ;
+doubleRange: leftEndpoint=('(' | '[') SPACE? DOUBLE SPACE? ':' SPACE? DOUBLE SPACE? rightEndpoint=(')' | ']') ;
+dateRange: leftEndpoint=('(' | '[') SPACE? DATE SPACE? ':' SPACE? DATE SPACE? rightEndpoint=(')' | ']') ;
+dateTimeRange: leftEndpoint=('(' | '[') SPACE? DATETIME SPACE? ':' SPACE? DATETIME SPACE? rightEndpoint=(')' | ']') ;
+versionRange: leftEndpoint=('(' | '[') SPACE? VERSION_NUMBER SPACE? ':' SPACE? VERSION_NUMBER SPACE? rightEndpoint=(')' | ']') ;
+STRING: '\'' (~('\'' | '\r' | '\n'))* '\'' ; // todo NAE-1997: escape???
+INT: DIGIT+ ;
+DOUBLE: DIGIT+ '.' DIGIT+ ;
+DATETIME: DATE 'T' ([01] DIGIT | '2' [0-3]) ':' [0-5] DIGIT ':' [0-5] DIGIT ('.' DIGIT+)? ; // 2020-03-03T20:00:00.055 // todo NAE-1997: format
+DATE: DIGIT DIGIT DIGIT DIGIT '-' ('0' [1-9] | '1' [0-2]) '-' ('0' [1-9] | [12] DIGIT | '3' [01]) ; // 2020-03-03 // todo NAE-1997: format
+BOOLEAN: T R U E | F A L S E ;
+VERSION_NUMBER: DIGIT+ '.' DIGIT+ '.' DIGIT+ ;
+JAVA_ID: [a-zA-Z$_] [a-zA-Z0-9$_]* ;
+
+SPACE: [ ]+ ;
+ANY: . ;
+
+// fragments
+fragment A : [aA];
+fragment B : [bB];
+fragment C : [cC];
+fragment D : [dD];
+fragment E : [eE];
+fragment F : [fF];
+fragment G : [gG];
+fragment H : [hH];
+fragment I : [iI];
+fragment J : [jJ];
+fragment K : [kK];
+fragment L : [lL];
+fragment M : [mM];
+fragment N : [nN];
+fragment O : [oO];
+fragment P : [pP];
+fragment Q : [qQ];
+fragment R : [rR];
+fragment S : [sS];
+fragment T : [tT];
+fragment U : [uU];
+fragment V : [vV];
+fragment W : [wW];
+fragment X : [xX];
+fragment Y : [yY];
+fragment Z : [zZ];
+fragment DIGIT: [0-9];
diff --git a/src/main/java/com/netgrif/application/engine/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/IResourceSearchService.java b/src/main/java/com/netgrif/application/engine/pfql/service/IResourceSearchService.java
new file mode 100644
index 00000000000..b097588e24f
--- /dev/null
+++ b/src/main/java/com/netgrif/application/engine/pfql/service/IResourceSearchService.java
@@ -0,0 +1,88 @@
+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 getQueryResourceType();
+
+ 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);
+
+ /**
+ * 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");
+ }
+ }
+
+ /**
+ * 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.");
+ }
+ }
+
+ /**
+ * 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.");
+ }
+ }
+
+ /**
+ * 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",
+ getQueryResourceType(), evaluator.getResourceType()));
+ }
+ }
+}
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..1ea796443f4
--- /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..34f9455511c
--- /dev/null
+++ b/src/main/java/com/netgrif/application/engine/pfql/service/QueryLangErrorListener.java
@@ -0,0 +1,39 @@
+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");
+ if (line < 1 || line > lines.length) {
+ return underlineErrorMsg;
+ }
+ 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..f7832efaa5a
--- /dev/null
+++ b/src/main/java/com/netgrif/application/engine/pfql/service/QueryLangEvaluator.java
@@ -0,0 +1,1436 @@
+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 {
+
+ // todo code encapsulation by resource type
+
+ private final ParseTreeProperty elasticQuery = new ParseTreeProperty<>();
+ private final ParseTreeProperty mongoQuery = new ParseTreeProperty<>();
+
+ @Getter
+ private QueryType resourceType;
+ @Getter
+ private Boolean multiple;
+ @Getter
+ @Setter
+ private Boolean searchWithElastic = false;
+ @Getter
+ private Predicate fullMongoQuery;
+ @Getter
+ private String fullElasticQuery;
+ @Getter
+ @Setter
+ 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) {
+ resourceType = 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) {
+ resourceType = 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) {
+ resourceType = 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) {
+ resourceType = 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 (resourceType) {
+ 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: " + resourceType);
+ }
+
+ 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 (resourceType) {
+ 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: " + resourceType);
+ }
+
+ 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 (resourceType) {
+ 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: " + resourceType);
+ }
+
+ 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 (resourceType) {
+ 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: " + resourceType);
+ }
+
+ 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 (resourceType) {
+ 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: " + resourceType);
+ }
+
+ 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 (resourceType) {
+ 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: " + resourceType);
+ }
+
+ 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 (resourceType) {
+ 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: " + resourceType);
+ }
+
+ 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 (resourceType) {
+ 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: " + resourceType);
+ }
+
+ 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 (resourceType) {
+ 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: " + resourceType);
+ }
+
+ 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) {
+ // todo implement task states
+// 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) {
+ // todo implement lastAssigned
+// 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) {
+ // todo implement lastAssigned
+// 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) {
+ // 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() ;
+// List stringDateList = terminalNodeList.stream().map(TerminalNode::getText).collect(Collectors.toList());
+//
+// setMongoQuery(ctx, buildDateTimePredicateInList(dateTimePath, stringDateList, not));
+ }
+
+ @Override
+ public void exitLaDateRange(QueryLangParser.LaDateRangeContext ctx) {
+ // todo implement lastAssigned
+// 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) {
+ // todo implement lastFinished
+// 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) {
+ // todo implement lastFinished
+// 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) {
+ // 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() ;
+// List stringDateList = terminalNodeList.stream().map(TerminalNode::getText).collect(Collectors.toList());
+//
+// setMongoQuery(ctx, buildDateTimePredicateInList(dateTimePath, stringDateList, not));
+ }
+
+ @Override
+ public void exitLfDateRange(QueryLangParser.LfDateRangeContext ctx) {
+ // todo implement lastFinished
+// 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) {
+ // todo implement task states
+// 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: 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..833527cd49a
--- /dev/null
+++ b/src/main/java/com/netgrif/application/engine/pfql/service/QueryLangExplainEvaluator.java
@@ -0,0 +1,507 @@
+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: 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());
+ return;
+ }
+ 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..f9a27017013
--- /dev/null
+++ b/src/main/java/com/netgrif/application/engine/pfql/service/SearchService.java
@@ -0,0 +1,102 @@
+package com.netgrif.application.engine.pfql.service;
+
+import com.netgrif.application.engine.pfql.domain.enums.QueryType;
+import com.netgrif.application.engine.pfql.service.utils.SearchUtils;
+import lombok.extern.slf4j.Slf4j;
+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
+public class SearchService implements ISearchService {
+
+ private final Map> serviceRegistry;
+
+ public SearchService(List> services) {
+ this.serviceRegistry = services.stream()
+ .collect(Collectors.toMap(IResourceSearchService::getQueryResourceType, Function.identity()));
+
+ }
+
+ /**
+ * 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);
+ 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;
+ }
+
+ /**
+ * 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) {
+ log.debug("Counting resources with query: {}", 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;
+ }
+
+ /**
+ * 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);
+ 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/caseresource/CaseSearchService.java b/src/main/java/com/netgrif/application/engine/pfql/service/caseresource/CaseSearchService.java
new file mode 100644
index 00000000000..9d9c77265f7
--- /dev/null
+++ b/src/main/java/com/netgrif/application/engine/pfql/service/caseresource/CaseSearchService.java
@@ -0,0 +1,233 @@
+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 {
+
+ 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;
+
+ /**
+ * Returns the query type handled by this service.
+ *
+ * @return the QueryType.CASE indicating this service handles case queries
+ */
+ @Override
+ public QueryType getQueryResourceType() {
+ 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) {
+ checkEvaluatorNotNull(evaluator);
+ checkEvaluatorIsSingle(evaluator);
+ checkEvaluatorResourceType(evaluator);
+
+ 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) {
+ checkEvaluatorNotNull(evaluator);
+ checkEvaluatorIsMultiple(evaluator);
+ checkEvaluatorResourceType(evaluator);
+
+ 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) {
+ checkEvaluatorNotNull(evaluator);
+ checkEvaluatorResourceType(evaluator);
+
+ 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) {
+ 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());
+ 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..6529d358e3b
--- /dev/null
+++ b/src/main/java/com/netgrif/application/engine/pfql/service/processresource/ProcessSearchService.java
@@ -0,0 +1,210 @@
+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 {
+
+ public static final String QUERY_SINGLE_PREFIX = "process: ";
+ public static final String QUERY_MULTIPLE_PREFIX = "processes: ";
+
+ 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 getQueryResourceType() {
+ 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) {
+ checkEvaluatorNotNull(evaluator);
+ checkEvaluatorIsSingle(evaluator);
+ checkEvaluatorResourceType(evaluator);
+
+ // todo implement Elasticsearch search (service layer and evaluator layer)
+
+ log.debug("Searching for single process using MongoDB");
+ log.trace("Executing MongoDB query: {}", evaluator.getFullMongoQuery());
+ PetriNet result = petriNetService.searchOne(evaluator.getFullMongoQuery());
+ log.trace("MongoDB search one result: {}", result != null ? result.getStringId() : "null");
+ return result;
+ }
+
+ /**
+ * 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) {
+ checkEvaluatorNotNull(evaluator);
+ checkEvaluatorIsMultiple(evaluator);
+ checkEvaluatorResourceType(evaluator);
+
+ // 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) {
+ checkEvaluatorNotNull(evaluator);
+ checkEvaluatorResourceType(evaluator);
+
+ // 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) {
+ checkEvaluatorNotNull(evaluator);
+ checkEvaluatorResourceType(evaluator);
+
+ // 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..fdce84b36e8
--- /dev/null
+++ b/src/main/java/com/netgrif/application/engine/pfql/service/taskresource/TaskSearchService.java
@@ -0,0 +1,264 @@
+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 {
+
+ 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;
+
+ /**
+ * Returns the query type handled by this service.
+ *
+ * @return {@link QueryType#TASK} indicating this service handles task queries
+ */
+ @Override
+ public QueryType getQueryResourceType() {
+ 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) {
+ checkEvaluatorNotNull(evaluator);
+ checkEvaluatorIsSingle(evaluator);
+ checkEvaluatorResourceType(evaluator);
+
+ 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) {
+ checkEvaluatorNotNull(evaluator);
+ checkEvaluatorIsMultiple(evaluator);
+ checkEvaluatorResourceType(evaluator);
+
+ 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) {
+ checkEvaluatorNotNull(evaluator);
+ checkEvaluatorResourceType(evaluator);
+
+ 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) {
+ 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());
+ 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/pfql/service/userresource/UserSearchService.java b/src/main/java/com/netgrif/application/engine/pfql/service/userresource/UserSearchService.java
new file mode 100644
index 00000000000..2ddbb86d8a9
--- /dev/null
+++ b/src/main/java/com/netgrif/application/engine/pfql/service/userresource/UserSearchService.java
@@ -0,0 +1,179 @@
+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;
+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;
+
+/**
+ * Service for searching and querying user resources using PFQL (Process Flow Query Language).
+ *
+ * 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}.
+ *
+ *
+ * @see IResourceSearchService
+ * @see IUserService
+ * @see QueryLangEvaluator
+ */
+@Slf4j
+@Service
+@RequiredArgsConstructor
+public class UserSearchService implements IResourceSearchService {
+
+ public static final String QUERY_SINGLE_PREFIX = "user: ";
+ public static final String QUERY_MULTIPLE_PREFIX = "users: ";
+
+ private final IUserService userService;
+
+ /**
+ * 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);
+ checkEvaluatorIsSingle(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;
+ }
+
+ /**
+ * 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 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 searchAll(QueryLangEvaluator evaluator) {
+ checkEvaluatorNotNull(evaluator);
+ checkEvaluatorIsMultiple(evaluator);
+ checkEvaluatorResourceType(evaluator);
+
+ 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;
+ }
+
+ /**
+ * 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);
+ 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;
+ }
+
+ /**
+ * 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);
+ 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/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..6aba1770f68
--- /dev/null
+++ b/src/main/java/com/netgrif/application/engine/pfql/service/utils/SearchUtils.java
@@ -0,0 +1,502 @@
+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.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;
+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.Set;
+import java.util.stream.Collectors;
+
+@Slf4j
+public class SearchUtils {
+
+ private static final Set 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> 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),
+ 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(
+ "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", e);
+ }
+ }
+ }
+
+ 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", e);
+ }
+ }
+ }
+
+ 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 && 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) {
+ 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.NEQ:
+ predicate = stringPath.ne(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("\\.");
+ 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;
+
+ Predicate predicate = null;
+ switch (op) {
+ 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)))
+ .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.NEQ:
+ predicate = dateTimePath.ne(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.NEQ:
+ query = attribute + ":" + value;
+ not = !not;
+ 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;
+ }
+
+ 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/main/java/com/netgrif/application/engine/workflow/service/CaseSearchService.java b/src/main/java/com/netgrif/application/engine/workflow/service/LegacyCaseSearchService.java
similarity index 97%
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..993fee2d6e8 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,9 @@
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());
+ private static final Logger log = LoggerFactory.getLogger(LegacyCaseSearchService.class.getName());
public static final String ROLE = "role";
public static final String DATA = "data";
@@ -89,12 +89,18 @@ public Predicate buildQuery(Map 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));
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/TaskSearchService.java b/src/main/java/com/netgrif/application/engine/workflow/service/LegacyTaskSearchService.java
similarity index 94%
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 4c5fe9b4a3a..7ce966aac50 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;
@@ -33,7 +33,7 @@ public Predicate buildQuery(List 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 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,17 +55,7 @@ public Predicate buildQuery(List requests, LoggedUser user, L
// (((Rp!=0 & Rn = 0) or Up!=0) & Un=0) == 1
constraints.andNot(buildNegativeViewUsersQueryConstraint(loggedOrImpersonated));
- return builder.and(constraints);
- }
-
- protected Predicate buildRolesQueryConstraint(LoggedUser user) {
- List 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) {
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 a06eec049f6..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
@@ -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;
@@ -82,7 +83,7 @@ public class TaskService implements ITaskService {
protected MongoTemplate mongoTemplate;
@Autowired
- protected TaskSearchService searchService;
+ protected LegacyTaskSearchService searchService;
@Autowired
@Qualifier("taskScheduler")
@@ -690,6 +691,29 @@ public long count(List requests, LoggedUser user, Locale loca
}
}
+ @Override
+ public long count(com.querydsl.core.types.Predicate predicate) {
+ 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) {
+ 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
public Page findByCases(Pageable pageable, List cases) {
return loadUsers(taskRepository.findByCaseIdIn(pageable, cases));
@@ -733,15 +757,22 @@ public Page searchAll(com.querydsl.core.types.Predicate predicate) {
@Override
public Page search(com.querydsl.core.types.Predicate predicate, Pageable pageable) {
- 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) {
- Page tasks = taskRepository.findAll(predicate, PageRequest.of(0, 1));
- if (tasks.getTotalElements() > 0)
+ Page tasks = search(predicate, PageRequest.of(0, 1));
+ if (tasks.getTotalElements() > 0) {
return tasks.getContent().get(0);
+ }
return null;
}
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 c9d434f7538..28b1e948714 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;
@@ -31,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;
@@ -75,7 +75,7 @@ public class WorkflowService implements IWorkflowService {
protected ITaskService taskService;
@Autowired
- protected CaseSearchService searchService;
+ protected LegacyCaseSearchService searchService;
@Autowired
protected ApplicationEventPublisher publisher;
@@ -186,8 +186,14 @@ 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);
}
@@ -215,6 +221,28 @@ public long count(Map request, LoggedUser user, Locale locale) {
}
}
+ @Override
+ public long count(Predicate predicate) {
+ 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) {
+ if (predicate == null) {
+ return false;
+ }
+
+ Predicate permissionConstraints = searchService.buildPermissionConstraints(userService.getLoggedOrSystem().transformToLoggedUser());
+ Predicate finalPredicate = ExpressionUtils.and(predicate, permissionConstraints);
+ return repository.exists(finalPredicate);
+ }
+
@Override
public Case resolveUserRef(Case useCase) {
useCase.getUsers().clear();
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/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/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/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..871ed934130
--- /dev/null
+++ b/src/test/groovy/com/netgrif/application/engine/pfql/QueryLangActionTest.groovy
@@ -0,0 +1,190 @@
+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 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
+
+@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("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))
+
+ 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("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))
+
+ 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("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))
+
+ 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("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))
+
+ 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/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
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/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 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 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> 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 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 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/QueryLangTest.java b/src/test/java/com/netgrif/application/engine/pfql/QueryLangTest.java
new file mode 100644
index 00000000000..92419009f07
--- /dev/null
+++ b/src/test/java/com/netgrif/application/engine/pfql/QueryLangTest.java
@@ -0,0 +1,2029 @@
+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 org.bson.Document;
+import org.bson.types.ObjectId;
+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;
+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;
+import static org.junit.jupiter.api.Assertions.*;
+
+@SpringBootTest
+@ActiveProfiles({"test"})
+@ExtendWith(SpringExtension.class)
+public class QueryLangTest {
+
+ public static final ObjectId GENERIC_OBJECT_ID = ObjectId.get();
+
+ @Autowired
+ private MongoOperations mongoOperations;
+
+ @Autowired
+ private ISearchService searchService;
+
+ @Autowired
+ private ImportHelper helper;
+
+ @Autowired
+ private TestHelper testHelper;
+
+ @Test
+ @SuppressWarnings("unchecked")
+ public void testSearchService() {
+ testHelper.truncateDbs();
+
+ Optional optionalPetriNet = helper.createNet("/pfql.xml");
+ assertNotNull(optionalPetriNet);
+
+ 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 params = new HashMap<>();
+ 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");
+ assertNotNull(cases);
+ assertEquals(PageImpl.class, cases.getClass());
+ assertEquals(10, ((Page) 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");
+ assertEquals(5, ((Page) cases).getTotalElements());
+
+ cases = searchService.search("cases: processIdentifier eq 'query_test' and data.boolean_0.value == true and data.text_0.value != '4'");
+ assertEquals(4, ((Page) cases).getTotalElements());
+ }
+
+ @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);
+
+ 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"));
+
+ 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);
+
+ 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"));
+
+ 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();
+ assertNull(actual);
+
+ // task state comparison
+ actual = evaluateQuery("case: tasks.t1.state eq enabled").getFullMongoQuery();
+ assertNull(actual);
+
+ // task userId comparison
+ actual = evaluateQuery("case: tasks.t1.userId eq 'test'").getFullMongoQuery();
+ assertNull(actual);
+
+ // data value comparison
+ 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);
+
+ actual = evaluateQuery("case: data.field1.value eq 1").getFullMongoQuery();
+ assertNull(actual);
+
+ actual = evaluateQuery("case: data.field1.value lt 1").getFullMongoQuery();
+ assertNull(actual);
+
+ actual = evaluateQuery("case: data.field1.value lte 1").getFullMongoQuery();
+ assertNull(actual);
+
+ actual = evaluateQuery("case: data.field1.value gt 1").getFullMongoQuery();
+ assertNull(actual);
+
+ actual = evaluateQuery("case: data.field1.value gte 1").getFullMongoQuery();
+ assertNull(actual);
+
+ actual = evaluateQuery("case: data.field1.value eq 2011-12-03T10:15:30").getFullMongoQuery();
+ assertNull(actual);
+
+ actual = evaluateQuery("case: data.field1.value lt 2011-12-03T10:15:30").getFullMongoQuery();
+ assertNull(actual);
+
+ actual = evaluateQuery("case: data.field1.value lte 2011-12-03T10:15:30").getFullMongoQuery();
+ assertNull(actual);
+
+ actual = evaluateQuery("case: data.field1.value gt 2011-12-03T10:15:30").getFullMongoQuery();
+ assertNull(actual);
+
+ actual = evaluateQuery("case: data.field1.value gte 2011-12-03T10:15:30").getFullMongoQuery();
+ assertNull(actual);
+
+ actual = evaluateQuery("case: data.field1.value eq true").getFullMongoQuery();
+ assertNull(actual);
+
+ // data options comparison
+ actual = evaluateQuery("case: data.field1.options eq 'test'").getFullMongoQuery();
+ assertNull(actual);
+
+ actual = evaluateQuery("case: data.field1.options contains 'test'").getFullMongoQuery();
+ assertNull(actual);
+ }
+
+ @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);
+
+ 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"));
+
+ 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);
+
+ 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"));
+
+ 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);
+
+ 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")));
+
+ 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);
+
+ 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"));
+
+ 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);
+
+ 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"));
+
+ 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);
+
+ 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")));
+
+ 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);
+
+ 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"));
+
+ 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);
+
+ 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"));
+
+ 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);
+
+ 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")));
+
+ 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();
+ assertNull(actual);
+
+ // identifier comparison
+ actual = evaluateQuery("process: identifier eq 'test'").getFullElasticQuery();
+ assertNull(actual);
+
+ actual = evaluateQuery("process: identifier contains 'test'").getFullElasticQuery();
+ assertNull(actual);
+
+ // version comparison
+ actual = evaluateQuery("process: version eq 1.1.1").getFullElasticQuery();
+ assertNull(actual);
+
+ actual = evaluateQuery("process: version lt 1.1.1").getFullElasticQuery();
+ assertNull(actual);
+
+ actual = evaluateQuery("process: version lte 1.1.1").getFullElasticQuery();
+ assertNull(actual);
+
+
+ actual = evaluateQuery("process: version gt 1.1.1").getFullElasticQuery();
+ assertNull(actual);
+
+
+ actual = evaluateQuery("process: version gte 1.1.1").getFullElasticQuery();
+ assertNull(actual);
+
+
+ // title comparison
+ actual = evaluateQuery("process: title eq 'test'").getFullElasticQuery();
+ assertNull(actual);
+
+
+ actual = evaluateQuery("process: title contains 'test'").getFullElasticQuery();
+ assertNull(actual);
+
+
+ // creationDate comparison
+ actual = evaluateQuery("process: creationDate eq 2011-12-03T10:15:30").getFullElasticQuery();
+ assertNull(actual);
+
+
+ actual = evaluateQuery("process: creationDate lt 2011-12-03T10:15:30").getFullElasticQuery();
+ assertNull(actual);
+
+
+ actual = evaluateQuery("process: creationDate lte 2011-12-03T10:15:30").getFullElasticQuery();
+ assertNull(actual);
+
+
+ actual = evaluateQuery("process: creationDate gt 2011-12-03T10:15:30").getFullElasticQuery();
+ assertNull(actual);
+
+
+ actual = evaluateQuery("process: creationDate gte 2011-12-03T10:15:30").getFullElasticQuery();
+ assertNull(actual);
+ }
+
+ @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();
+ assertNull(actual);
+ assertNull(actual);
+
+ actual = evaluateQuery(String.format("process: id neq '%s'", GENERIC_OBJECT_ID)).getFullElasticQuery();
+ assertNull(actual);
+
+ // and comparison
+ actual = evaluateQuery(String.format("process: id eq '%s' and title eq 'test'", GENERIC_OBJECT_ID)).getFullElasticQuery();
+ assertNull(actual);
+
+ // and not comparison
+ actual = evaluateQuery(String.format("process: id eq '%s' and title not eq 'test'", GENERIC_OBJECT_ID)).getFullElasticQuery();
+ assertNull(actual);
+
+ actual = evaluateQuery(String.format("process: id eq '%s' and title != 'test'", GENERIC_OBJECT_ID)).getFullElasticQuery();
+ assertNull(actual);
+
+ // or comparison
+ actual = evaluateQuery(String.format("process: id eq '%s' or title eq 'test'", GENERIC_OBJECT_ID)).getFullElasticQuery();
+ assertNull(actual);
+
+ // or not comparison
+ actual = evaluateQuery(String.format("process: id eq '%s' or title not eq 'test'", GENERIC_OBJECT_ID)).getFullElasticQuery();
+ assertNull(actual);
+
+ actual = evaluateQuery(String.format("process: id eq '%s' or title != 'test'", GENERIC_OBJECT_ID)).getFullElasticQuery();
+ assertNull(actual);
+
+ // parenthesis comparison
+ actual = evaluateQuery(String.format("process: id eq '%s' and (title eq 'test' or title eq 'test1')", GENERIC_OBJECT_ID)).getFullElasticQuery();
+ 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();
+ 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();
+ assertNull(actual);
+ }
+
+ @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);
+ 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);
+ 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);
+ 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);
+ assertEquals(expected, 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";
+ assertEquals(expected, actual);
+
+ actual = evaluateQuery("case: author contains 'test'").getFullElasticQuery();
+ expected = "author:*test*";
+ 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);
+ assertEquals(expected, 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);
+// assertEquals(expected, actual);
+// actual = evaluateQuery("case: tasks.t1.state eq disabled").getFullElasticQuery();
+// expected = String.format("tasks.t1.state:%s", State.DISABLED);
+// assertEquals(expected, actual);
+
+ // task userId comparison
+ actual = evaluateQuery("case: tasks.t1.userId eq 'test'").getFullElasticQuery();
+ expected = "tasks.t1.userId:test";
+ assertEquals(expected, actual);
+
+ actual = evaluateQuery("case: tasks.t1.userId contains 'test'").getFullElasticQuery();
+ expected = "tasks.t1.userId:*test*";
+ 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);
+ assertEquals(expected, 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";
+ assertEquals(expected, actual);
+
+ actual = evaluateQuery("case: data.field1.value eq false").getFullElasticQuery();
+ expected = "dataSet.field1.booleanValue:false";
+ assertEquals(expected, 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);
+ assertEquals(expected, actual);
+
+ actual = evaluateQuery(String.format("case: id neq '%s'", GENERIC_OBJECT_ID)).getFullElasticQuery();
+ expected = String.format("NOT stringId:%s", GENERIC_OBJECT_ID);
+ 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);
+ 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);
+ 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);
+ 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);
+ 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);
+ 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);
+ 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);
+ 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);
+ assertEquals(expected, 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();
+ assertNull(actual);
+
+ // transitionId comparison
+ actual = evaluateQuery("task: transitionId eq 'test'").getFullElasticQuery();
+ assertNull(actual);
+
+ actual = evaluateQuery("task: transitionId contains 'test'").getFullElasticQuery();
+ assertNull(actual);
+
+ // title comparison
+ actual = evaluateQuery("task: title eq 'test'").getFullElasticQuery();
+ assertNull(actual);
+
+ actual = evaluateQuery("task: title contains 'test'").getFullElasticQuery();
+ assertNull(actual);
+
+ // state comparison
+ actual = evaluateQuery("task: state eq enabled").getFullElasticQuery();
+ assertNull(actual);
+
+ actual = evaluateQuery("task: state eq disabled").getFullElasticQuery();
+ assertNull(actual);
+
+ // userId comparison
+ actual = evaluateQuery("task: userId eq 'test'").getFullElasticQuery();
+ assertNull(actual);
+
+ actual = evaluateQuery("task: userId contains 'test'").getFullElasticQuery();
+ assertNull(actual);
+
+ // caseId comparison
+ actual = evaluateQuery("task: caseId eq 'test'").getFullElasticQuery();
+ assertNull(actual);
+
+ actual = evaluateQuery("task: caseId contains 'test'").getFullElasticQuery();
+ assertNull(actual);
+
+ // processId comparison
+ actual = evaluateQuery("task: processId eq 'test'").getFullElasticQuery();
+ assertNull(actual);
+
+ actual = evaluateQuery("task: processId contains 'test'").getFullElasticQuery();
+ assertNull(actual);
+
+ // lastAssign comparison
+ actual = evaluateQuery("task: lastAssign eq 2011-12-03T10:15:30").getFullElasticQuery();
+ assertNull(actual);
+
+ actual = evaluateQuery("task: lastAssign lt 2011-12-03T10:15:30").getFullElasticQuery();
+ assertNull(actual);
+
+ actual = evaluateQuery("task: lastAssign lte 2011-12-03T10:15:30").getFullElasticQuery();
+ assertNull(actual);
+
+ actual = evaluateQuery("task: lastAssign gt 2011-12-03T10:15:30").getFullElasticQuery();
+ assertNull(actual);
+
+ actual = evaluateQuery("task: lastAssign gte 2011-12-03T10:15:30").getFullElasticQuery();
+ assertNull(actual);
+
+ // lastFinish comparison
+ actual = evaluateQuery("task: lastFinish eq 2011-12-03T10:15:30").getFullElasticQuery();
+ assertNull(actual);
+
+ actual = evaluateQuery("task: lastFinish lt 2011-12-03T10:15:30").getFullElasticQuery();
+ assertNull(actual);
+
+ actual = evaluateQuery("task: lastFinish lte 2011-12-03T10:15:30").getFullElasticQuery();
+ assertNull(actual);
+
+ actual = evaluateQuery("task: lastFinish gt 2011-12-03T10:15:30").getFullElasticQuery();
+ assertNull(actual);
+
+ actual = evaluateQuery("task: lastFinish gte 2011-12-03T10:15:30").getFullElasticQuery();
+ assertNull(actual);
+ }
+
+ @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();
+ assertNull(actual);
+
+ actual = evaluateQuery(String.format("task: id neq '%s'", GENERIC_OBJECT_ID)).getFullElasticQuery();
+ assertNull(actual);
+
+ // and comparison
+ actual = evaluateQuery(String.format("task: id eq '%s' and title eq 'test'", GENERIC_OBJECT_ID)).getFullElasticQuery();
+ assertNull(actual);
+
+ // and not comparison
+ actual = evaluateQuery(String.format("task: id eq '%s' and title not eq 'test'", GENERIC_OBJECT_ID)).getFullElasticQuery();
+ assertNull(actual);
+
+ actual = evaluateQuery(String.format("task: id eq '%s' and title != 'test'", GENERIC_OBJECT_ID)).getFullElasticQuery();
+ assertNull(actual);
+
+ // or comparison
+ actual = evaluateQuery(String.format("task: id eq '%s' or title eq 'test'", GENERIC_OBJECT_ID)).getFullElasticQuery();
+ assertNull(actual);
+
+ // or not comparison
+ actual = evaluateQuery(String.format("task: id eq '%s' or title not eq 'test'", GENERIC_OBJECT_ID)).getFullElasticQuery();
+ assertNull(actual);
+
+ actual = evaluateQuery(String.format("task: id eq '%s' or title neq 'test'", GENERIC_OBJECT_ID)).getFullElasticQuery();
+ assertNull(actual);
+
+ // parenthesis comparison
+ actual = evaluateQuery(String.format("task: id eq '%s' and (title eq 'test' or title eq 'test1')", GENERIC_OBJECT_ID)).getFullElasticQuery();
+ 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();
+ 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();
+ assertNull(actual);
+ }
+
+ @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();
+ assertNull(actual);
+
+ // name comparison
+ actual = evaluateQuery("user: name eq 'test'").getFullElasticQuery();
+ assertNull(actual);
+
+ actual = evaluateQuery("user: name contains 'test'").getFullElasticQuery();
+ assertNull(actual);
+
+ // surname comparison
+ actual = evaluateQuery("user: surname eq 'test'").getFullElasticQuery();
+ assertNull(actual);
+
+ actual = evaluateQuery("user: surname contains 'test'").getFullElasticQuery();
+ assertNull(actual);
+
+ // email comparison
+ actual = evaluateQuery("user: email eq 'test'").getFullElasticQuery();
+ assertNull(actual);
+
+ actual = evaluateQuery("user: email contains 'test'").getFullElasticQuery();
+ assertNull(actual);
+ }
+
+ @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();
+ assertNull(actual);
+
+ actual = evaluateQuery(String.format("user: id != '%s'", GENERIC_OBJECT_ID)).getFullElasticQuery();
+ assertNull(actual);
+
+ // and comparison
+ actual = evaluateQuery(String.format("user: id eq '%s' and email eq 'test'", GENERIC_OBJECT_ID)).getFullElasticQuery();
+ assertNull(actual);
+
+ // and not comparison
+ actual = evaluateQuery(String.format("user: id eq '%s' and email not eq 'test'", GENERIC_OBJECT_ID)).getFullElasticQuery();
+ assertNull(actual);
+
+ actual = evaluateQuery(String.format("user: id eq '%s' and email neq 'test'", GENERIC_OBJECT_ID)).getFullElasticQuery();
+ assertNull(actual);
+
+ // or comparison
+ actual = evaluateQuery(String.format("user: id eq '%s' or email eq 'test'", GENERIC_OBJECT_ID)).getFullElasticQuery();
+ assertNull(actual);
+
+ // or not comparison
+ actual = evaluateQuery(String.format("user: id eq '%s' or email not eq 'test'", GENERIC_OBJECT_ID)).getFullElasticQuery();
+ assertNull(actual);
+
+ actual = evaluateQuery(String.format("user: id eq '%s' or email != 'test'", GENERIC_OBJECT_ID)).getFullElasticQuery();
+ assertNull(actual);
+
+ // parenthesis comparison
+ actual = evaluateQuery(String.format("user: id eq '%s' and (email eq 'test' or email eq 'test1')", GENERIC_OBJECT_ID)).getFullElasticQuery();
+ 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();
+ 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();
+ assertNull(actual);
+ }
+
+ @Test
+ public void testPagingQuery() {
+ Pageable pageable = evaluateQuery("cases: processIdentifier eq 'test'").getPageable();
+ assertEquals(0, pageable.getPageNumber());
+ assertEquals(20, pageable.getPageSize());
+
+ pageable = evaluateQuery("cases: processIdentifier eq 'test' page 2").getPageable();
+ assertEquals(2, pageable.getPageNumber());
+ assertEquals(20, pageable.getPageSize());
+
+ pageable = evaluateQuery("cases: processIdentifier eq 'test' page 2 size 4").getPageable();
+ assertEquals(2, pageable.getPageNumber());
+ assertEquals(4, pageable.getPageSize());
+ }
+
+ @Test
+ public void testProcessSortingQuery() {
+ // default (no sort)
+ Pageable actual = evaluateQuery("processes: identifier eq 'test'").getPageable();
+ assertFalse(actual.getSort().isSorted());
+
+ // default ordering asc
+ actual = evaluateQuery("processes: identifier eq 'test' sort by id").getPageable();
+ assertTrue(actual.getSort().isSorted());
+ List orders = actual.getSort().toList();
+ 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();
+ assertTrue(actual.getSort().isSorted());
+ orders = actual.getSort().toList();
+ 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();
+ assertTrue(actual.getSort().isSorted());
+ orders = actual.getSort().toList();
+ 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();
+ assertTrue(actual.getSort().isSorted());
+ orders = actual.getSort().toList();
+ 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();
+ assertTrue(actual.getSort().isSorted());
+ orders = actual.getSort().toList();
+ 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();
+ assertTrue(actual.getSort().isSorted());
+ orders = actual.getSort().toList();
+ 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();
+ assertTrue(actual.getSort().isSorted());
+ orders = actual.getSort().toList();
+ 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();
+ assertTrue(actual.getSort().isSorted());
+ orders = actual.getSort().toList();
+ 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();
+ assertFalse(actual.getSort().isSorted());
+
+ // default ordering asc
+ actual = evaluateQuery("cases: processIdentifier eq 'test' sort by id").getPageable();
+ assertTrue(actual.getSort().isSorted());
+ List orders = actual.getSort().toList();
+ 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();
+ assertTrue(actual.getSort().isSorted());
+ orders = actual.getSort().toList();
+ 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();
+ assertTrue(actual.getSort().isSorted());
+ orders = actual.getSort().toList();
+ 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();
+ assertTrue(actual.getSort().isSorted());
+ orders = actual.getSort().toList();
+ 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();
+ assertTrue(actual.getSort().isSorted());
+ orders = actual.getSort().toList();
+ 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();
+ assertTrue(actual.getSort().isSorted());
+ orders = actual.getSort().toList();
+ 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();
+ assertTrue(actual.getSort().isSorted());
+ orders = actual.getSort().toList();
+ 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();
+ assertTrue(actual.getSort().isSorted());
+ orders = actual.getSort().toList();
+ 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();
+ assertTrue(actual.getSort().isSorted());
+ orders = actual.getSort().toList();
+ 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();
+ assertFalse(actual.getSort().isSorted());
+
+ // default ordering asc
+ actual = evaluateQuery("cases: data.field1.value eq 'test' sort by id").getPageable();
+ assertTrue(actual.getSort().isSorted());
+ List orders = actual.getSort().toList();
+ 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();
+ assertTrue(actual.getSort().isSorted());
+ orders = actual.getSort().toList();
+ 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();
+ assertTrue(actual.getSort().isSorted());
+ orders = actual.getSort().toList();
+ 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();
+ assertTrue(actual.getSort().isSorted());
+ orders = actual.getSort().toList();
+ 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();
+ assertTrue(actual.getSort().isSorted());
+ orders = actual.getSort().toList();
+ 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();
+ assertTrue(actual.getSort().isSorted());
+ orders = actual.getSort().toList();
+ 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();
+ assertTrue(actual.getSort().isSorted());
+ orders = actual.getSort().toList();
+ 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();
+ assertTrue(actual.getSort().isSorted());
+ orders = actual.getSort().toList();
+ 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();
+ assertTrue(actual.getSort().isSorted());
+ orders = actual.getSort().toList();
+ 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();
+ assertTrue(actual.getSort().isSorted());
+ orders = actual.getSort().toList();
+ 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();
+ assertTrue(actual.getSort().isSorted());
+ orders = actual.getSort().toList();
+ 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();
+ assertTrue(actual.getSort().isSorted());
+ orders = actual.getSort().toList();
+ 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();
+ assertFalse(actual.getSort().isSorted());
+
+ // default ordering asc
+ actual = evaluateQuery("tasks: title eq 'test' sort by id").getPageable();
+ assertTrue(actual.getSort().isSorted());
+ List orders = actual.getSort().toList();
+ 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();
+ assertTrue(actual.getSort().isSorted());
+ orders = actual.getSort().toList();
+ 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();
+ assertTrue(actual.getSort().isSorted());
+ orders = actual.getSort().toList();
+ 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();
+ assertTrue(actual.getSort().isSorted());
+ orders = actual.getSort().toList();
+ 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();
+ assertTrue(actual.getSort().isSorted());
+ orders = actual.getSort().toList();
+ 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();
+ assertTrue(actual.getSort().isSorted());
+ orders = actual.getSort().toList();
+ 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();
+ assertTrue(actual.getSort().isSorted());
+ orders = actual.getSort().toList();
+ 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();
+ assertTrue(actual.getSort().isSorted());
+ orders = actual.getSort().toList();
+ 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();
+ assertTrue(actual.getSort().isSorted());
+ orders = actual.getSort().toList();
+ 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();
+ assertTrue(actual.getSort().isSorted());
+ orders = actual.getSort().toList();
+ 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();
+ assertTrue(actual.getSort().isSorted());
+ orders = actual.getSort().toList();
+ 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();
+ assertFalse(actual.getSort().isSorted());
+
+ // default ordering asc
+ actual = evaluateQuery("users: name eq 'test' sort by id").getPageable();
+ assertTrue(actual.getSort().isSorted());
+ List orders = actual.getSort().toList();
+ 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();
+ assertTrue(actual.getSort().isSorted());
+ orders = actual.getSort().toList();
+ 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();
+ assertTrue(actual.getSort().isSorted());
+ orders = actual.getSort().toList();
+ 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();
+ assertTrue(actual.getSort().isSorted());
+ orders = actual.getSort().toList();
+ 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();
+ assertTrue(actual.getSort().isSorted());
+ orders = actual.getSort().toList();
+ 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();
+ assertTrue(actual.getSort().isSorted());
+ orders = actual.getSort().toList();
+ 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();
+ assertTrue(actual.getSort().isSorted());
+ orders = actual.getSort().toList();
+ 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
+ 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
+ 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
+ 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
+ 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
+ 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
+ assertThrows(IllegalArgumentException.class, () -> evaluateQuery("case: places.p1.marking contains 1"));
+
+ // date/datetime comparison
+ assertThrows(IllegalArgumentException.class, () -> evaluateQuery("case: creationDate contains 2020-03-03"));
+
+ // boolean comparison
+ 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) {
+ 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);
+
+ assertEquals(expected, actual);
+
+ actual = evaluateQuery(String.format("%s: %s contains 'test'", resource, attribute)).getFullElasticQuery();
+ expected = String.format("%s:*test*", resultAttribute);
+
+ assertEquals(expected, actual);
+
+ actual = evaluateQuery(String.format("%s: %s lt 'test'", resource, attribute)).getFullElasticQuery();
+ expected = String.format("%s:test", resultAttribute);
+
+ assertEquals(expected, actual);
+
+ actual = evaluateQuery(String.format("%s: %s gte 'test'", resource, attribute)).getFullElasticQuery();
+ expected = String.format("%s:>=test", resultAttribute);
+
+ 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);
+
+ 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);
+
+ assertEquals(expected, actual);
+
+ 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);
+
+ 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);
+
+ 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);
+
+ assertEquals(expected, actual);
+
+ actual = evaluateQuery(String.format("%s: %s lt 1", resource, attribute)).getFullElasticQuery();
+ expected = String.format("%s:<1", resultAttribute);
+
+ assertEquals(expected, actual);
+
+ actual = evaluateQuery(String.format("%s: %s lte 1", resource, attribute)).getFullElasticQuery();
+ expected = String.format("%s:<=1", resultAttribute);
+
+ assertEquals(expected, actual);
+
+ actual = evaluateQuery(String.format("%s: %s gt 1", resource, attribute)).getFullElasticQuery();
+ expected = String.format("%s:>1", resultAttribute);
+
+ assertEquals(expected, actual);
+
+ actual = evaluateQuery(String.format("%s: %s gte 1", resource, attribute)).getFullElasticQuery();
+ expected = String.format("%s:>=1", resultAttribute);
+
+ 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);
+
+ 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);
+
+ 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);
+
+ 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);
+
+ 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);
+
+ assertEquals(expected, actual);
+ }
+
+ 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());
+
+ 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());
+
+ 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());
+
+ 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());
+
+ 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());
+
+ 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());
+
+ 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());
+
+ 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());
+
+ 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());
+
+ 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());
+
+ 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());
+
+ 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());
+
+ 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());
+
+ 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());
+
+ 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());
+
+ assertEquals(expected, actual);
+ }
+
+ private static void compareMongoQueries(MongoDbUtils> mongoDbUtils, Predicate actual, Predicate expected) {
+ Document actualDocument = mongoDbUtils.convertPredicateToDocument(actual);
+ Document expectedDocument = mongoDbUtils.convertPredicateToDocument(expected);
+
+ assertEquals(expectedDocument, actualDocument);
+ }
+}
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 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 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 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/java/com/netgrif/application/engine/pfql/utils/MongoDbUtils.java b/src/test/java/com/netgrif/application/engine/pfql/utils/MongoDbUtils.java
new file mode 100644
index 00000000000..5fc8d04ffe5
--- /dev/null
+++ b/src/test/java/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 extends T> type) {
+ super(operations, type, operations.getCollectionName(type));
+ }
+
+ public Document convertPredicateToDocument(Predicate predicate) {
+ return this.createQuery(predicate);
+ }
+}
diff --git a/src/test/java/com/netgrif/application/engine/pfql/utils/SearchTestUtils.java b/src/test/java/com/netgrif/application/engine/pfql/utils/SearchTestUtils.java
new file mode 100644
index 00000000000..b4ed91b129e
--- /dev/null
+++ b/src/test/java/com/netgrif/application/engine/pfql/utils/SearchTestUtils.java
@@ -0,0 +1,58 @@
+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());
+
+ actualIds.sort(String::compareTo);
+ expectedIds.sort(String::compareTo);
+ assert actualIds.equals(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/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 @@
all_data
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
+
+