diff --git a/pom.xml b/pom.xml index 24550b4ead7..6daac6a006a 100644 --- a/pom.xml +++ b/pom.xml @@ -64,6 +64,7 @@ 7.70.0.Final netgrif-oss https://sonarcloud.io + 4.13.1 2.15.0-rc1 @@ -573,6 +574,12 @@ minio 8.5.12 + + + org.antlr + antlr4-runtime + ${antlr4.version} + @@ -838,6 +845,22 @@ license-maven-plugin 2.0.0 + + org.antlr + antlr4-maven-plugin + ${antlr4.version} + + src/main/java + ${project.build.directory}/generated-sources/java/ + + + + + antlr4 + + + + diff --git a/src/main/groovy/com/netgrif/application/engine/petrinet/domain/dataset/logic/action/ActionDelegate.groovy b/src/main/groovy/com/netgrif/application/engine/petrinet/domain/dataset/logic/action/ActionDelegate.groovy index ab5df4a7356..2179ff7641c 100644 --- a/src/main/groovy/com/netgrif/application/engine/petrinet/domain/dataset/logic/action/ActionDelegate.groovy +++ b/src/main/groovy/com/netgrif/application/engine/petrinet/domain/dataset/logic/action/ActionDelegate.groovy @@ -48,16 +48,15 @@ import com.netgrif.application.engine.petrinet.domain.version.Version import com.netgrif.application.engine.petrinet.service.interfaces.IPetriNetService import com.netgrif.application.engine.petrinet.service.interfaces.IProcessRoleService import com.netgrif.application.engine.petrinet.service.interfaces.IUriService +import com.netgrif.application.engine.pfql.service.IResourceSearchService +import com.netgrif.application.engine.pfql.service.ISearchService +import com.netgrif.application.engine.pfql.service.utils.SearchUtils import com.netgrif.application.engine.rules.domain.RuleRepository import com.netgrif.application.engine.startup.DefaultFiltersRunner import com.netgrif.application.engine.startup.FilterRunner import com.netgrif.application.engine.startup.ImportHelper import com.netgrif.application.engine.utils.FullPageRequest -import com.netgrif.application.engine.workflow.domain.Case -import com.netgrif.application.engine.workflow.domain.DataField -import com.netgrif.application.engine.workflow.domain.QCase -import com.netgrif.application.engine.workflow.domain.QTask -import com.netgrif.application.engine.workflow.domain.Task +import com.netgrif.application.engine.workflow.domain.* import com.netgrif.application.engine.workflow.domain.eventoutcomes.EventOutcome import com.netgrif.application.engine.workflow.domain.eventoutcomes.caseoutcomes.CreateCaseEventOutcome import com.netgrif.application.engine.workflow.domain.eventoutcomes.dataoutcomes.GetDataEventOutcome @@ -88,7 +87,6 @@ import org.springframework.data.domain.Pageable import java.time.ZoneId import java.util.stream.Collectors - /** * ActionDelegate class contains Actions API methods. */ @@ -210,6 +208,21 @@ class ActionDelegate { @Autowired DashboardItemService dashboardItemService + @Autowired + ISearchService searchService + + @Autowired + IResourceSearchService caseSearchService + + @Autowired + IResourceSearchService taskSearchService + + @Autowired + IResourceSearchService processSearchService + + @Autowired + IResourceSearchService userSearchService + FrontendActionOutcome Frontend /** @@ -3185,4 +3198,476 @@ class ActionDelegate { Case taskCase = workflowService.findOne(task.caseId) return taskCase.getPetriNet().getDataSet().get(fieldId) } + + /** + * Searches for a single {@link Case} matching the given query. + *

+ * The query must start with the resource keyword {@code case} (singular). + *

+ * Example: + *
+     *     searchCase("case: processIdentifier eq 'query_test' and data.number_0.value == 3")
+     *     searchCase("case: id eq '5f9b1c2d3e4f5a6b7c8d9e0f'")
+     *     searchCase("id eq '5f9b1c2d3e4f5a6b7c8d9e0f'")
+     * 
+ * + * @param query query language string starting with {@code case:} + * @return matching {@link Case} or {@code null} if none is found + */ + Case searchCase(String query) { + query = SearchUtils.ensureStartsWithCase(query) + return caseSearchService.searchOne(query) + } + + /** + * Searches for all {@link Case} instances matching the given query and returns a paged result. + *

+ * The query must start with the resource keyword {@code cases} (plural) and may contain + * paging and sorting clauses. + *

+ * Example: + *
+     *     pagedSearchCases("cases: processIdentifier eq 'query_test' page 1 size 5 sort by title desc")
+     *     pagedSearchCases("cases: author eq 'user@mail.com' and creationDate gt 2020-03-03")
+     *     pagedSearchCases("author eq 'user@mail.com' and creationDate gt 2020-03-03")
+     * 
+ * + * @param query query language string starting with {@code cases:} + * @return {@link Page} of matching cases + */ + Page pagedSearchCases(String query) { + query = SearchUtils.ensureStartsWithCases(query) + return caseSearchService.searchAll(query) + } + + /** + * Searches for all {@link Case} instances matching the given query and returns them as a list. + *

+ * The query must start with the resource keyword {@code cases} (plural). This is a convenience + * method returning only the content of {@link #pagedSearchCases(String)}. + *

+ * Example: + *
+     *     searchCases("cases: processIdentifier eq 'query_test' and data.boolean_0.value == true")
+     *     searchCases("cases: title contains 'Test' sort by creationDate desc")
+     *     searchCases("title contains 'Test' sort by creationDate desc")
+     * 
+ * + * @param query query language string starting with {@code cases:} + * @return list of matching cases + */ + List searchCases(String query) { + return pagedSearchCases(query).content + } + + /** + * Counts the number of {@link Case} instances matching the given query. + *

+ * The query must start with the resource keyword {@code cases} (plural). + *

+ * Example: + *
+     *     countCases("cases: processIdentifier eq 'query_test'")
+     *     countCases("cases: data.boolean_0.value == true and data.text_0.value != '4'")
+     *     countCases("data.boolean_0.value == true and data.text_0.value != '4'")
+     * 
+ * + * @param query query language string starting with {@code cases:} + * @return number of matching cases + */ + long countCases(String query) { + query = SearchUtils.ensureStartsWithCases(query) + return caseSearchService.count(query) + } + + /** + * Checks whether at least one {@link Case} matching the given query exists. + *

+ * The query must start with the resource keyword {@code cases} (plural). + *

+ * Example: + *
+     *     existsCase("cases: processIdentifier eq 'query_test'")
+     *     existsCase("cases: id in ('5f9b1c2d3e4f5a6b7c8d9e0f', '5f9b1c2d3e4f5a6b7c8d9e10')")
+     *     existsCase("id in ('5f9b1c2d3e4f5a6b7c8d9e0f', '5f9b1c2d3e4f5a6b7c8d9e10')")
+     * 
+ * + * @param query query language string starting with {@code cases:} + * @return {@code true} if a matching case exists, {@code false} otherwise + */ + boolean existsCase(String query) { + query = SearchUtils.ensureStartsWithCases(query) + return caseSearchService.exists(query) + } + + /** + * Searches for a single {@link Task} matching the given query. + *

+ * The query must start with the resource keyword {@code task} (singular). + *

+ * Example: + *
+     *     searchTask("task: transitionId eq 't1' and caseId eq '5f9b1c2d3e4f5a6b7c8d9e0f'")
+     *     searchTask("task: id eq '5f9b1c2d3e4f5a6b7c8d9e0f'")
+     *     searchTask("id eq '5f9b1c2d3e4f5a6b7c8d9e0f'")
+     * 
+ * + * @param query query language string starting with {@code task:} + * @return matching {@link Task} or {@code null} if none is found + */ + Task searchTask(String query) { + query = SearchUtils.ensureStartsWithTask(query) + return taskSearchService.searchOne(query) + } + + /** + * Searches for all {@link Task} instances matching the given query and returns a paged result. + *

+ * The query must start with the resource keyword {@code tasks} (plural) and may contain + * paging and sorting clauses. + *

+ * Example: + *
+     *     pagedSearchTasks("tasks: title eq 'test' page 0 size 10 sort by lastFinish desc")
+     *     pagedSearchTasks("tasks: userId eq 'user1' and state eq enabled")
+     *     pagedSearchTasks("userId eq 'user1' and state eq enabled")
+     * 
+ * + * @param query query language string starting with {@code tasks:} + * @return {@link Page} of matching tasks + */ + Page pagedSearchTasks(String query) { + query = SearchUtils.ensureStartsWithTasks(query) + return taskSearchService.searchAll(query) + } + + /** + * Searches for all {@link Task} instances matching the given query and returns them as a list. + *

+ * The query must start with the resource keyword {@code tasks} (plural). This is a convenience + * method returning only the content of {@link #pagedSearchTasks(String)}. + *

+ * Example: + *
+     *     searchTasks("tasks: processId eq 'my_process' and userId in ('user1', 'user2')")
+     *     searchTasks("tasks: title contains 'Approve' sort by title asc")
+     *     searchTasks("title contains 'Approve' sort by title asc")
+     * 
+ * + * @param query query language string starting with {@code tasks:} + * @return list of matching tasks + */ + List searchTasks(String query) { + query = SearchUtils.ensureStartsWithTasks(query) + return pagedSearchTasks(query).content + } + + /** + * Counts the number of {@link Task} instances matching the given query. + *

+ * The query must start with the resource keyword {@code tasks} (plural). + *

+ * Example: + *
+     *     countTasks("tasks: caseId eq '5f9b1c2d3e4f5a6b7c8d9e0f'")
+     *     countTasks("tasks: transitionId eq 't1' and userId eq 'user1'")
+     *     countTasks("transitionId eq 't1' and userId eq 'user1'")
+     * 
+ * + * @param query query language string starting with {@code tasks:} + * @return number of matching tasks + */ + long countTasks(String query) { + query = SearchUtils.ensureStartsWithTasks(query) + return taskSearchService.count(query) + } + + /** + * Checks whether at least one {@link Task} matching the given query exists. + *

+ * The query must start with the resource keyword {@code tasks} (plural). + *

+ * Example: + *
+     *     existsTask("tasks: caseId eq '5f9b1c2d3e4f5a6b7c8d9e0f'")
+     *     existsTask("tasks: transitionId eq 't1' and userId not eq 'user1'")
+     *     existsTask("transitionId eq 't1' and userId not eq 'user1'")
+     * 
+ * + * @param query query language string starting with {@code tasks:} + * @return {@code true} if a matching task exists, {@code false} otherwise + */ + boolean existsTask(String query) { + query = SearchUtils.ensureStartsWithTasks(query) + return taskSearchService.exists(query) + } + + /** + * Searches for a single {@link PetriNet} (process) matching the given query. + *

+ * The query must start with the resource keyword {@code process} (singular). + *

+ * Example: + *
+     *     searchProcess("process: identifier == 'query_test'")
+     *     searchProcess("process: identifier eq 'my_process' and version eq 1.0.0")
+     *     searchProcess("identifier eq 'my_process' and version eq 1.0.0")
+     * 
+ * + * @param query query language string starting with {@code process:} + * @return matching {@link PetriNet} or {@code null} if none is found + */ + PetriNet searchProcess(String query) { + query = SearchUtils.ensureStartsWithProcess(query) + return processSearchService.searchOne(query) + } + + /** + * Searches for all {@link PetriNet} (process) instances matching the given query and returns a paged result. + *

+ * The query must start with the resource keyword {@code processes} (plural) and may contain + * paging and sorting clauses. + *

+ * Example: + *
+     *     pagedSearchProcesses("processes: identifier eq 'my_process' page 0 size 10 sort by version desc")
+     *     pagedSearchProcesses("processes: version in (1.0.0 : 2.0.0)")
+     *     pagedSearchProcesses("version in (1.0.0 : 2.0.0)")
+     * 
+ * + * @param query query language string starting with {@code processes:} + * @return {@link Page} of matching processes + */ + Page pagedSearchProcesses(String query) { + query = SearchUtils.ensureStartsWithProcesses(query) + return processSearchService.searchAll(query) + } + + /** + * Searches for all {@link PetriNet} (process) instances matching the given query and returns them as a list. + *

+ * The query must start with the resource keyword {@code processes} (plural). This is a convenience + * method returning only the content of {@link #pagedSearchProcesses(String)}. + *

+ * Example: + *
+     *     searchProcesses("processes: title contains 'Test' sort by identifier asc")
+     *     searchProcesses("processes: identifier in ('process_a', 'process_b')")
+     *     searchProcesses("identifier in ('process_a', 'process_b')")
+     * 
+ * + * @param query query language string starting with {@code processes:} + * @return list of matching processes + */ + List searchProcesses(String query) { + query = SearchUtils.ensureStartsWithProcesses(query) + return pagedSearchProcesses(query).content + } + + /** + * Counts the number of {@link PetriNet} (process) instances matching the given query. + *

+ * The query must start with the resource keyword {@code processes} (plural). + *

+ * Example: + *
+     *     countProcesses("processes: identifier eq 'my_process'")
+     *     countProcesses("processes: version gte 1.0.0")
+     *     countProcesses("version gte 1.0.0")
+     * 
+ * + * @param query query language string starting with {@code processes:} + * @return number of matching processes + */ + long countProcesses(String query) { + query = SearchUtils.ensureStartsWithProcesses(query) + return processSearchService.count(query) + } + + /** + * Checks whether at least one {@link PetriNet} (process) matching the given query exists. + *

+ * The query must start with the resource keyword {@code processes} (plural). + *

+ * Example: + *
+     *     existsProcess("processes: identifier eq 'my_process'")
+     *     existsProcess("processes: version eq 1.0.0")
+     *     existsProcess("version eq 1.0.0")
+     * 
+ * + * @param query query language string starting with {@code processes:} + * @return {@code true} if a matching process exists, {@code false} otherwise + */ + boolean existsProcess(String query) { + query = SearchUtils.ensureStartsWithProcesses(query) + return processSearchService.exists(query) + } + + /** + * Searches for a single {@link IUser} matching the given query. + *

+ * The query must start with the resource keyword {@code user} (singular). + *

+ * Example: + *
+     *     searchUser("user: email eq 'user@mail.com'")
+     *     searchUser("user: name eq 'John' and surname eq 'Doe'")
+     *     searchUser("name eq 'John' and surname eq 'Doe'")
+     * 
+ * + * @param query query language string starting with {@code user:} + * @return matching {@link IUser} or {@code null} if none is found + */ + IUser searchUser(String query) { + query = SearchUtils.ensureStartsWithUser(query) + return userSearchService.searchOne(query) + } + + /** + * Searches for all {@link IUser} instances matching the given query and returns a paged result. + *

+ * The query must start with the resource keyword {@code users} (plural) and may contain + * paging and sorting clauses. + *

+ * Example: + *
+     *     pagedSearchUsers("users: name eq 'John' page 0 size 25 sort by surname asc")
+     *     pagedSearchUsers("users: email contains '@company.com'")
+     *     pagedSearchUsers("email contains '@company.com'")
+     * 
+ * + * @param query query language string starting with {@code users:} + * @return {@link Page} of matching users + */ + Page pagedSearchUsers(String query) { + query = SearchUtils.ensureStartsWithUsers(query) + return userSearchService.searchAll(query) + } + + /** + * Searches for all {@link IUser} instances matching the given query and returns them as a list. + *

+ * 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("users: surname eq 'Doe' sort by name asc")
+     *     searchUsers("users: email in ('a@mail.com', 'b@mail.com')")
+     *     searchUsers("email in ('a@mail.com', 'b@mail.com')")
+     * 
+ * + * @param query query language string starting with {@code users:} + * @return list of matching users + */ + List 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 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 + + true + + test_role TEST diff --git a/src/test/resources/petriNets/pfql.xml b/src/test/resources/petriNets/pfql.xml new file mode 100644 index 00000000000..1dd151d8199 --- /dev/null +++ b/src/test/resources/petriNets/pfql.xml @@ -0,0 +1,97 @@ + + query_test + 1.0.0 + NEW + New Model + device_hub + true + true + false + + boolean_0 + Boolean + (params["id"] as int) % 2 == 0 + + + dateTime_0 + Date Time + java.time.LocalDateTime.now().plusYears(params["id"] as int) + + + number_0 + Number + params["id"] as int + + + text_0 + Text + params["id"] + + + t1 + 176 + 112 + + + t1_0 + 4 + grid + + text_0 + + editable + + + 0 + 0 + 1 + 2 + + outline + + + + number_0 + + editable + + + 2 + 0 + 1 + 2 + + outline + + + + boolean_0 + + editable + + + 0 + 1 + 1 + 2 + + outline + + + + dateTime_0 + + editable + + + 2 + 1 + 1 + 2 + + outline + + + + + \ No newline at end of file diff --git a/src/test/resources/petriNets/query_lang_test.xml b/src/test/resources/petriNets/query_lang_test.xml new file mode 100644 index 00000000000..fcbec702dae --- /dev/null +++ b/src/test/resources/petriNets/query_lang_test.xml @@ -0,0 +1,246 @@ + + query_lang_test + QLT + query_lang_test + device_hub + true + false + false + + + input_query + + </data> + + <data type="text"> + <id>result_class</id> + <title/> + </data> + + <data type="text"> + <id>result</id> + <title/> + </data> + + <data type="button"> + <id>general_search</id> + <title/> + <event type="set"> + <actions phase="post"> + <action> + input_query: f.input_query, + result_class: f.result_class; + + change result_class value { search(input_query.value)?.class?.toString() } + </action> + </actions> + </event> + </data> + <data type="button"> + <id>general_count</id> + <title/> + <event type="set"> + <actions phase="post"> + <action> + input_query: f.input_query, + result: f.result; + + change result value { count(input_query.value) } + </action> + </actions> + </event> + </data> + <data type="button"> + <id>general_exists</id> + <title/> + <event type="set"> + <actions phase="post"> + <action> + input_query: f.input_query, + result: f.result; + + change result value { exists(input_query.value) } + </action> + </actions> + </event> + </data> + + <data type="button"> + <id>user_search_one</id> + <title/> + <event type="set"> + <actions phase="post"> + <action> + input_query: f.input_query, + result_class: f.result_class; + + change result_class value { searchUser(input_query.value)?.class?.toString() } + </action> + </actions> + </event> + </data> + <data type="button"> + <id>user_search</id> + <title/> + <event type="set"> + <actions phase="post"> + <action> + input_query: f.input_query, + result_class: f.result_class; + + change result_class value { searchUsers(input_query.value)?.class?.toString()} + </action> + </actions> + </event> + </data> + <data type="button"> + <id>user_paged_search</id> + <title/> + <event type="set"> + <actions phase="post"> + <action> + input_query: f.input_query, + result_class: f.result_class; + + change result_class value { pagedSearchUsers(input_query.value)?.class?.toString() } + </action> + </actions> + </event> + </data> + + <data type="button"> + <id>task_search_one</id> + <title/> + <event type="set"> + <actions phase="post"> + <action> + input_query: f.input_query, + result_class: f.result_class; + + change result_class value { searchTask(input_query.value)?.class?.toString() } + </action> + </actions> + </event> + </data> + <data type="button"> + <id>task_search</id> + <title/> + <event type="set"> + <actions phase="post"> + <action> + input_query: f.input_query, + result_class: f.result_class; + + change result_class value { searchTasks(input_query.value)?.class?.toString() } + </action> + </actions> + </event> + </data> + <data type="button"> + <id>task_paged_search</id> + <title/> + <event type="set"> + <actions phase="post"> + <action> + input_query: f.input_query, + result_class: f.result_class; + + change result_class value { pagedSearchTasks(input_query.value)?.class?.toString() } + </action> + </actions> + </event> + </data> + + <data type="button"> + <id>process_search_one</id> + <title/> + <event type="set"> + <actions phase="post"> + <action> + input_query: f.input_query, + result_class: f.result_class; + + change result_class value { searchProcess(input_query.value)?.class?.toString() } + </action> + </actions> + </event> + </data> + <data type="button"> + <id>process_search</id> + <title/> + <event type="set"> + <actions phase="post"> + <action> + input_query: f.input_query, + result_class: f.result_class; + + change result_class value { searchProcesses(input_query.value)?.class?.toString() } + </action> + </actions> + </event> + </data> + <data type="button"> + <id>process_paged_search</id> + <title/> + <event type="set"> + <actions phase="post"> + <action> + input_query: f.input_query, + result_class: f.result_class; + + change result_class value { pagedSearchProcesses(input_query.value)?.class?.toString() } + </action> + </actions> + </event> + </data> + + <data type="button"> + <id>case_search_one</id> + <title/> + <event type="set"> + <actions phase="post"> + <action> + input_query: f.input_query, + result_class: f.result_class; + + change result_class value { searchCase(input_query.value)?.class?.toString() } + </action> + </actions> + </event> + </data> + <data type="button"> + <id>case_search</id> + <title/> + <event type="set"> + <actions phase="post"> + <action> + input_query: f.input_query, + result_class: f.result_class; + + change result_class value { searchCases(input_query.value)?.class?.toString() } + </action> + </actions> + </event> + </data> + <data type="button"> + <id>case_paged_search</id> + <title/> + <event type="set"> + <actions phase="post"> + <action> + input_query: f.input_query, + result_class: f.result_class; + + change result_class value { pagedSearchCases(input_query.value)?.class?.toString() } + </action> + </actions> + </event> + </data> + + <transition> + <id>t1</id> + <x>700</x> + <y>180</y> + <label>TEST API</label> + </transition> +</document> \ No newline at end of file diff --git a/src/test/resources/variable_arc_test.xml b/src/test/resources/variable_arc_test.xml index 199883e5db9..dcbc0bb503d 100644 --- a/src/test/resources/variable_arc_test.xml +++ b/src/test/resources/variable_arc_test.xml @@ -3,6 +3,7 @@ <id>variable_arc_test.xml</id> <initials>TST</initials> <title>Test + true