diff --git a/src/main/groovy/com/netgrif/application/engine/validation/models/CaseFilterFieldValidation.groovy b/src/main/groovy/com/netgrif/application/engine/validation/models/CaseFilterFieldValidation.groovy new file mode 100644 index 00000000000..62ea151c481 --- /dev/null +++ b/src/main/groovy/com/netgrif/application/engine/validation/models/CaseFilterFieldValidation.groovy @@ -0,0 +1,12 @@ +package com.netgrif.application.engine.validation.models + +import com.netgrif.application.engine.pfql.domain.enums.QueryType +import com.netgrif.application.engine.validation.domain.ValidationDataInput + +class CaseFilterFieldValidation extends FilterFieldValidation { + + @Override + void query(ValidationDataInput validationData) { + doValidation(validationData, QueryType.CASE) + } +} diff --git a/src/main/groovy/com/netgrif/application/engine/validation/models/FilterFieldValidation.groovy b/src/main/groovy/com/netgrif/application/engine/validation/models/FilterFieldValidation.groovy new file mode 100644 index 00000000000..4a3f5ae891e --- /dev/null +++ b/src/main/groovy/com/netgrif/application/engine/validation/models/FilterFieldValidation.groovy @@ -0,0 +1,52 @@ +package com.netgrif.application.engine.validation.models + +import com.netgrif.application.engine.pfql.domain.enums.QueryType +import com.netgrif.application.engine.pfql.service.QueryLangEvaluator +import com.netgrif.application.engine.pfql.service.utils.SearchUtils +import com.netgrif.application.engine.validation.domain.ValidationDataInput + +/** + * Abstract base class for validating filter field values against PFQL queries. + *

+ * This class provides common validation logic for filter fields that need to verify + * whether their values represent valid PFQL queries of a specific type. + *

+ */ +abstract class FilterFieldValidation extends AbstractFieldValidation { + + /** + * Validates the filter field value as a PFQL query. + *

+ * Implementations must specify the expected query type for validation. + *

+ * + * @param validationData the validation data input containing the field value and validation context + */ + abstract void query(ValidationDataInput validationData) + + /** + * Performs validation of the field value against a specific PFQL query type. + *

+ * This method checks if the field value is a valid PFQL query and verifies that + * it matches the expected query type. If the value is null or empty, no validation + * is performed. If the query is invalid or does not match the expected type, + * an IllegalArgumentException is thrown with the localized validation message. + *

+ * + * @param validationData the validation data input containing the field value, locale, and validation message + * @param queryType the expected PFQL query type that the field value should match + * @throws IllegalArgumentException if the query is invalid or does not match the expected type + */ + protected static void doValidation(ValidationDataInput validationData, QueryType queryType) { + if (validationData.getData().getValue() != null && validationData.getData().getValue() != "") { + try { + QueryLangEvaluator evaluator = SearchUtils.evaluateQuery(validationData.getData().getValue() as String) + if (evaluator.resourceType !== queryType) { + throw new IllegalArgumentException(validationData.getValidationMessage().getTranslation(validationData.getLocale())) + } + } catch (Exception ignore) { + throw new IllegalArgumentException(validationData.getValidationMessage().getTranslation(validationData.getLocale())) + } + } + } +} \ No newline at end of file diff --git a/src/main/groovy/com/netgrif/application/engine/validation/models/ProcessFilterValidation.groovy b/src/main/groovy/com/netgrif/application/engine/validation/models/ProcessFilterValidation.groovy new file mode 100644 index 00000000000..df8a47bef6b --- /dev/null +++ b/src/main/groovy/com/netgrif/application/engine/validation/models/ProcessFilterValidation.groovy @@ -0,0 +1,12 @@ +package com.netgrif.application.engine.validation.models + +import com.netgrif.application.engine.pfql.domain.enums.QueryType +import com.netgrif.application.engine.validation.domain.ValidationDataInput + +class ProcessFilterValidation extends FilterFieldValidation { + + @Override + void query(ValidationDataInput validationData) { + doValidation(validationData, QueryType.PROCESS) + } +} diff --git a/src/main/groovy/com/netgrif/application/engine/validation/models/TaskFilterFieldValidation.groovy b/src/main/groovy/com/netgrif/application/engine/validation/models/TaskFilterFieldValidation.groovy new file mode 100644 index 00000000000..b1d333b05dd --- /dev/null +++ b/src/main/groovy/com/netgrif/application/engine/validation/models/TaskFilterFieldValidation.groovy @@ -0,0 +1,12 @@ +package com.netgrif.application.engine.validation.models + +import com.netgrif.application.engine.pfql.domain.enums.QueryType +import com.netgrif.application.engine.validation.domain.ValidationDataInput + +class TaskFilterFieldValidation extends FilterFieldValidation{ + + @Override + void query(ValidationDataInput validationData) { + doValidation(validationData, QueryType.TASK) + } +} diff --git a/src/main/groovy/com/netgrif/application/engine/validation/service/ValidationService.groovy b/src/main/groovy/com/netgrif/application/engine/validation/service/ValidationService.groovy index d15959dd44b..952386b3ef3 100644 --- a/src/main/groovy/com/netgrif/application/engine/validation/service/ValidationService.groovy +++ b/src/main/groovy/com/netgrif/application/engine/validation/service/ValidationService.groovy @@ -17,7 +17,7 @@ import java.util.stream.Collectors class ValidationService implements IValidationService { @Override - public void valid(Field field, DataField dataField) { + void valid(Field field, DataField dataField) { if (field.getValidations() == null) { return } @@ -36,6 +36,13 @@ class ValidationService implements IValidationService { instance = new BooleanFieldValidation() } else if (field instanceof DateField) { instance = new DateFieldValidation() + } else if (field instanceof CaseFilterField) { + instance = new CaseFilterFieldValidation() + } else if (field instanceof TaskFilterField) { + instance = new TaskFilterFieldValidation() + } else if (field instanceof ProcessFilterField) { + instance = new ProcessFilterValidation() + } // } else if (field instanceof DateTimeField) { // instance = new DateTimeFieldValidation() // } else if (field instanceof ButtonField) { @@ -61,13 +68,12 @@ class ValidationService implements IValidationService { // } else if (field instanceof UserListField) { // // } else if (field instanceof I18nField) { - } MetaMethod method = instance.metaClass.getMethods().find { it.name.toLowerCase() == rules.first().toLowerCase() } if (method != null) { I18nString validMessage = validation.getValidationMessage() != null ? validation.getValidationMessage() : new I18nString("Invalid Field value") method.invoke(instance, new ValidationDataInput(dataField, validMessage, LocaleContextHolder.getLocale(), rules.stream().skip(1).collect(Collectors.joining(" ")))) } else { - log.warn("Method [" + rules.first() + "] in dataField " + field.getImportId() + " not found") + log.warn("Method [{}] in dataField {} not found", rules.first(), field.getImportId()) } } }) diff --git a/src/main/java/com/netgrif/application/engine/importer/service/FieldFactory.java b/src/main/java/com/netgrif/application/engine/importer/service/FieldFactory.java index 850e4dbe0be..b6dddb4db71 100644 --- a/src/main/java/com/netgrif/application/engine/importer/service/FieldFactory.java +++ b/src/main/java/com/netgrif/application/engine/importer/service/FieldFactory.java @@ -33,6 +33,8 @@ @Slf4j public final class FieldFactory { + private static final String FILTER_FIELD_VALIDATION_RULE = "query"; + @Value("${nae.storage.default-type:local}") private String defaultStorageType; @@ -559,18 +561,21 @@ private FileListField buildFileListField(Data data) { private CaseFilterField buildCaseFilterField(Data data) { CaseFilterField field = new CaseFilterField(); setDefaultValue(field, data, field::setDefaultValue); + field.setValidations(getFilterValidationAsList()); return field; } private TaskFilterField buildTaskFilterField(Data data) { TaskFilterField field = new TaskFilterField(); setDefaultValue(field, data, field::setDefaultValue); + field.setValidations(getFilterValidationAsList()); return field; } private ProcessFilterField buildProcessFilterField(Data data) { ProcessFilterField field = new ProcessFilterField(); setDefaultValue(field, data, field::setDefaultValue); + field.setValidations(getFilterValidationAsList()); return field; } @@ -863,4 +868,11 @@ private Map getFieldOptions(MapOptionsField field, Cas private void resolveStorage(Data data, StorageField field) { field.setStorage(StorageFactory.createStorage(data, storageResolverService, defaultStorageType)); } + + private List getFilterValidationAsList() { + com.netgrif.application.engine.petrinet.domain.dataset.logic.validation.Validation defaultValidation = makeValidation(FILTER_FIELD_VALIDATION_RULE, null, false); + List validations = new ArrayList<>(); + validations.add(defaultValidation); + return validations; + } } diff --git a/src/main/java/com/netgrif/application/engine/menu/domain/Menu.java b/src/main/java/com/netgrif/application/engine/menu/domain/Menu.java deleted file mode 100644 index cce9388ba21..00000000000 --- a/src/main/java/com/netgrif/application/engine/menu/domain/Menu.java +++ /dev/null @@ -1,51 +0,0 @@ -package com.netgrif.application.engine.menu.domain; - -import com.fasterxml.jackson.dataformat.xml.annotation.JacksonXmlElementWrapper; -import com.fasterxml.jackson.dataformat.xml.annotation.JacksonXmlProperty; -import lombok.AllArgsConstructor; -import lombok.Data; -import lombok.NoArgsConstructor; - -import javax.xml.bind.annotation.XmlAccessType; -import javax.xml.bind.annotation.XmlAccessorType; -import javax.xml.bind.annotation.XmlRootElement; -import javax.xml.bind.annotation.XmlType; -import java.util.List; -import java.util.Objects; - -@Data -@NoArgsConstructor -@AllArgsConstructor -@XmlAccessorType(XmlAccessType.FIELD) -@XmlType(name = "", propOrder = { - "menuEntries", -}) - -@XmlRootElement(name = "menu") -public class Menu { - - @JacksonXmlElementWrapper(useWrapping = false) - @JacksonXmlProperty(localName = "menuEntry") - protected List menuEntries; - - @JacksonXmlProperty(isAttribute = true, localName = "name") - protected String menuIdentifier; - - @Override - public boolean equals(Object o) { - if (this == o) return true; - if (o == null || getClass() != o.getClass()) return false; - - Menu menu = (Menu) o; - - if (!Objects.equals(menuEntries, menu.menuEntries)) return false; - return Objects.equals(menuIdentifier, menu.menuIdentifier); - } - - @Override - public int hashCode() { - int result = menuEntries != null ? menuEntries.hashCode() : 0; - result = 31 * result + (menuIdentifier != null ? menuIdentifier.hashCode() : 0); - return result; - } -} diff --git a/src/main/java/com/netgrif/application/engine/menu/domain/MenuAndFilters.java b/src/main/java/com/netgrif/application/engine/menu/domain/MenuAndFilters.java deleted file mode 100644 index e12f019fabe..00000000000 --- a/src/main/java/com/netgrif/application/engine/menu/domain/MenuAndFilters.java +++ /dev/null @@ -1,54 +0,0 @@ -package com.netgrif.application.engine.menu.domain; - -import com.fasterxml.jackson.dataformat.xml.annotation.JacksonXmlElementWrapper; -import com.fasterxml.jackson.dataformat.xml.annotation.JacksonXmlProperty; -import com.fasterxml.jackson.dataformat.xml.annotation.JacksonXmlRootElement; -import com.netgrif.application.engine.workflow.domain.filter.FilterImportExportList; -import lombok.AllArgsConstructor; -import lombok.Data; - -import javax.xml.bind.annotation.XmlAccessType; -import javax.xml.bind.annotation.XmlAccessorType; -import javax.xml.bind.annotation.XmlType; -import java.util.Objects; - -@Data -@AllArgsConstructor -@XmlAccessorType(XmlAccessType.FIELD) -@XmlType(name = "", propOrder = { - "menuList", - "filterList" -}) - -@JacksonXmlRootElement(localName = "menusWithFilters") -public class MenuAndFilters { - @JacksonXmlElementWrapper(useWrapping = false) - @JacksonXmlProperty(localName = "menus") - protected MenuList menuList; - @JacksonXmlElementWrapper(useWrapping = false) - @JacksonXmlProperty(localName = "filters") - protected FilterImportExportList filterList; - - public MenuAndFilters() { - this.menuList = new MenuList(); - this.filterList = new FilterImportExportList(); - } - - @Override - public boolean equals(Object o) { - if (this == o) return true; - if (o == null || getClass() != o.getClass()) return false; - - MenuAndFilters that = (MenuAndFilters) o; - - if (!Objects.equals(menuList, that.menuList)) return false; - return Objects.equals(filterList, that.filterList); - } - - @Override - public int hashCode() { - int result = menuList != null ? menuList.hashCode() : 0; - result = 31 * result + (filterList != null ? filterList.hashCode() : 0); - return result; - } -} diff --git a/src/main/java/com/netgrif/application/engine/menu/domain/MenuEntry.java b/src/main/java/com/netgrif/application/engine/menu/domain/MenuEntry.java deleted file mode 100644 index c3900ddf511..00000000000 --- a/src/main/java/com/netgrif/application/engine/menu/domain/MenuEntry.java +++ /dev/null @@ -1,58 +0,0 @@ -package com.netgrif.application.engine.menu.domain; - -import com.fasterxml.jackson.dataformat.xml.annotation.JacksonXmlElementWrapper; -import com.fasterxml.jackson.dataformat.xml.annotation.JacksonXmlProperty; -import lombok.AllArgsConstructor; -import lombok.Data; -import lombok.EqualsAndHashCode; -import lombok.NoArgsConstructor; - -import javax.xml.bind.annotation.XmlAccessType; -import javax.xml.bind.annotation.XmlAccessorType; -import javax.xml.bind.annotation.XmlElement; -import javax.xml.bind.annotation.XmlType; -import java.util.List; -import java.util.Objects; - -@Data -@NoArgsConstructor -@AllArgsConstructor -@XmlAccessorType(XmlAccessType.FIELD) -@XmlType(name = "menuEntry", propOrder = { - "entryName", - "menuEntryRoleList", -}) -public class MenuEntry { - - @XmlElement(required = true) - protected String entryName; - @EqualsAndHashCode.Exclude - @XmlElement(required = true) - protected String filterCaseId; - @JacksonXmlElementWrapper(useWrapping = false) - @JacksonXmlProperty(localName = "entryRole") - protected List menuEntryRoleList; - @JacksonXmlProperty(isAttribute = true) - protected Boolean useIcon; - - @Override - public boolean equals(Object o) { - if (this == o) return true; - if (o == null || getClass() != o.getClass()) return false; - - MenuEntry menuEntry = (MenuEntry) o; - - if (!Objects.equals(entryName, menuEntry.entryName)) return false; - if (!Objects.equals(menuEntryRoleList, menuEntry.menuEntryRoleList)) - return false; - return Objects.equals(useIcon, menuEntry.useIcon); - } - - @Override - public int hashCode() { - int result = entryName != null ? entryName.hashCode() : 0; - result = 31 * result + (menuEntryRoleList != null ? menuEntryRoleList.hashCode() : 0); - result = 31 * result + (useIcon != null ? useIcon.hashCode() : 0); - return result; - } -} \ No newline at end of file diff --git a/src/main/java/com/netgrif/application/engine/menu/domain/MenuEntryRole.java b/src/main/java/com/netgrif/application/engine/menu/domain/MenuEntryRole.java deleted file mode 100644 index dd39ea86e8d..00000000000 --- a/src/main/java/com/netgrif/application/engine/menu/domain/MenuEntryRole.java +++ /dev/null @@ -1,51 +0,0 @@ -package com.netgrif.application.engine.menu.domain; - -import com.fasterxml.jackson.dataformat.xml.annotation.JacksonXmlProperty; -import com.netgrif.application.engine.workflow.domain.AuthorizationType; -import lombok.AllArgsConstructor; -import lombok.Data; -import lombok.NoArgsConstructor; - -import javax.xml.bind.annotation.XmlAccessType; -import javax.xml.bind.annotation.XmlAccessorType; -import javax.xml.bind.annotation.XmlElement; -import javax.xml.bind.annotation.XmlType; -import java.util.Objects; - -@Data -@NoArgsConstructor -@AllArgsConstructor -@XmlAccessorType(XmlAccessType.FIELD) -@XmlType(name = "", propOrder = { - "roleImportId", - "netImportId" -}) -public class MenuEntryRole { - - @XmlElement(required = true) - protected String roleImportId; - @XmlElement(required = true) - protected String netImportId; - @JacksonXmlProperty(localName = "type", isAttribute = true) - protected AuthorizationType authorizationType; - - @Override - public boolean equals(Object o) { - if (this == o) return true; - if (o == null || getClass() != o.getClass()) return false; - - MenuEntryRole that = (MenuEntryRole) o; - - if (!Objects.equals(roleImportId, that.roleImportId)) return false; - if (!Objects.equals(netImportId, that.netImportId)) return false; - return authorizationType == that.authorizationType; - } - - @Override - public int hashCode() { - int result = roleImportId != null ? roleImportId.hashCode() : 0; - result = 31 * result + (netImportId != null ? netImportId.hashCode() : 0); - result = 31 * result + (authorizationType != null ? authorizationType.hashCode() : 0); - return result; - } -} \ No newline at end of file diff --git a/src/main/java/com/netgrif/application/engine/menu/domain/MenuList.java b/src/main/java/com/netgrif/application/engine/menu/domain/MenuList.java deleted file mode 100644 index e76f0f7e40e..00000000000 --- a/src/main/java/com/netgrif/application/engine/menu/domain/MenuList.java +++ /dev/null @@ -1,47 +0,0 @@ -package com.netgrif.application.engine.menu.domain; - -import com.fasterxml.jackson.dataformat.xml.annotation.JacksonXmlElementWrapper; -import com.fasterxml.jackson.dataformat.xml.annotation.JacksonXmlProperty; -import lombok.AllArgsConstructor; -import lombok.Data; - -import javax.xml.bind.annotation.XmlAccessType; -import javax.xml.bind.annotation.XmlAccessorType; -import javax.xml.bind.annotation.XmlRootElement; -import javax.xml.bind.annotation.XmlType; -import java.util.ArrayList; -import java.util.List; - -@Data -@AllArgsConstructor -@XmlAccessorType(XmlAccessType.FIELD) -@XmlType(name = "", propOrder = { - "menus", -}) - -@XmlRootElement(name = "menus") -public class MenuList { - - @JacksonXmlElementWrapper(useWrapping = false) - @JacksonXmlProperty(localName = "menu") - protected List menus; - - public MenuList() { - this.menus = new ArrayList<>(); - } - - @Override - public boolean equals(Object o) { - if (this == o) return true; - if (o == null || getClass() != o.getClass()) return false; - - MenuList menuList = (MenuList) o; - - return menus.equals(menuList.menus); - } - - @Override - public int hashCode() { - return menus.hashCode(); - } -} diff --git a/src/main/java/com/netgrif/application/engine/pfql/domain/antlr4/QueryLang.g4 b/src/main/java/com/netgrif/application/engine/pfql/domain/antlr4/QueryLang.g4 index 55867f52899..f1b5d5533d0 100644 --- a/src/main/java/com/netgrif/application/engine/pfql/domain/antlr4/QueryLang.g4 +++ b/src/main/java/com/netgrif/application/engine/pfql/domain/antlr4/QueryLang.g4 @@ -139,71 +139,90 @@ userComparisons: idComparison // attribute comparisons idComparison: ID SPACE objectIdComparison # idBasic | ID SPACE inListStringComparison # idList + | ID SPACE nullComparison # idNull ; titleComparison: TITLE SPACE stringComparison # titleBasic + | TITLE SPACE stringLikeComparison # titleLike | TITLE SPACE inListStringComparison # titleList | TITLE SPACE inRangeStringComparison # titleRange + | TITLE SPACE nullComparison # titleNull ; identifierComparison: IDENTIFIER SPACE stringComparison # identifierBasic | IDENTIFIER SPACE inListStringComparison # identifierList | IDENTIFIER SPACE inRangeStringComparison # identifierRange + | IDENTIFIER SPACE nullComparison # identifierNull ; versionComparison: VERSION SPACE (NOT SPACE?)? op=(EQ | LT | GT | LTE | GTE) SPACE VERSION_NUMBER # versionBasic | VERSION SPACE inListVersionComparison # versionListCmp | VERSION SPACE inRangeVersionComparison # versionRangeCmp + | VERSION SPACE nullComparison # versionNull ; creationDateComparison: CREATION_DATE SPACE dateComparison # cdDateBasic | CREATION_DATE SPACE dateTimeComparison # cdDateTimeBasic | CREATION_DATE SPACE inListDateComparison # cdDateList | CREATION_DATE SPACE inRangeDateComparison # cdDateRange + | CREATION_DATE SPACE nullComparison # cdNull ; processIdComparison: PROCESS_ID SPACE stringComparison # processIdBasic | PROCESS_ID SPACE inListStringComparison # processIdList + | PROCESS_ID SPACE nullComparison # processIdNull ; processIdObjIdComparison: PROCESS_ID SPACE objectIdComparison # processIdObjIdBasic | PROCESS_ID SPACE inListStringComparison # processIdObjIdList + | PROCESS_ID SPACE nullComparison # processIdObjNull ; processIdentifierComparison: PROCESS_IDENTIFIER SPACE stringComparison # processIdentifierBasic | PROCESS_IDENTIFIER SPACE inListStringComparison # processIdentifierList | PROCESS_IDENTIFIER SPACE inRangeStringComparison # processIdentifierRange + | PROCESS_IDENTIFIER SPACE nullComparison # processIdentifierNull ; authorComparison: AUTHOR SPACE stringComparison # authorBasic | AUTHOR SPACE inListStringComparison # authorList + | AUTHOR SPACE nullComparison # authorNull ; transitionIdComparison: TRANSITION_ID SPACE stringComparison # transitionIdBasic | TRANSITION_ID SPACE inListStringComparison # transitionIdList | TRANSITION_ID SPACE inRangeStringComparison # transitionIdRange + | TRANSITION_ID SPACE nullComparison # transitionIdNull ; stateComparison: STATE SPACE EQ SPACE state=(ENABLED | DISABLED) ; userIdComparison: USER_ID SPACE stringComparison # userIdBasic | USER_ID SPACE inListStringComparison # userIdList + | USER_ID SPACE nullComparison # userIdNull ; caseIdComparison: CASE_ID SPACE stringComparison # caseIdBasic | CASE_ID SPACE inListStringComparison # caseIdList + | CASE_ID SPACE nullComparison # caseIdNull ; lastAssignComparison: LAST_ASSIGN SPACE dateComparison # laDateBasic | LAST_ASSIGN SPACE dateTimeComparison # laDateTimeBasic | LAST_ASSIGN SPACE inListDateComparison # laDateList | LAST_ASSIGN SPACE inRangeDateComparison # laDateRange + | LAST_ASSIGN SPACE nullComparison # laNull ; lastFinishComparison: LAST_FINISH SPACE dateComparison # lfDateBasic | LAST_FINISH SPACE dateTimeComparison # lfDateTimeBasic | LAST_FINISH SPACE inListDateComparison # lfDateList | LAST_FINISH SPACE inRangeDateComparison # lfDateRange + | LAST_FINISH SPACE nullComparison # lfNull ; nameComparison: NAME SPACE stringComparison # nameBasic | NAME SPACE inListStringComparison # nameList | NAME SPACE inRangeStringComparison # nameRange + | NAME SPACE nullComparison # nameNull ; surnameComparison: SURNAME SPACE stringComparison # surnameBasic | SURNAME SPACE inListStringComparison # surnameList | SURNAME SPACE inRangeStringComparison # surnameRange + | SURNAME SPACE nullComparison # surnameNull ; emailComparison: EMAIL SPACE stringComparison # emailBasic | EMAIL SPACE inListStringComparison # emailList | EMAIL SPACE inRangeStringComparison # emailRange + | EMAIL SPACE nullComparison # emailNull ; dataValueComparison: dataValue SPACE stringComparison # dataString + | dataValue SPACE stringLikeComparison # dataStringLike | dataValue SPACE numberComparison # dataNumber | dataValue SPACE dateComparison # dataDate | dataValue SPACE dateTimeComparison # dataDatetime @@ -214,27 +233,33 @@ dataValueComparison: dataValue SPACE stringComparison # dataString | dataValue SPACE inRangeStringComparison # dataStringRange | dataValue SPACE inRangeNumberComparison # dataNumberRange | dataValue SPACE inRangeDateComparison # dataDateRange + | dataValue SPACE nullComparison # dataNull ; dataOptionsComparison: dataOptions SPACE stringComparison # dataOptionsBasic | dataOptions SPACE inListStringComparison # dataOptionsList | dataOptions SPACE inRangeStringComparison # dataOptionsRange + | dataOptions SPACE nullComparison # dataOptionsNull ; placesComparison: places SPACE numberComparison # placesBasic | places SPACE inListNumberComparison # placesList | places SPACE inRangeNumberComparison # placesRange + | places SPACE nullComparison # placesNull ; tasksStateComparison: tasksState SPACE (NOT SPACE?)? op=EQ SPACE state=(ENABLED | DISABLED) ; tasksUserIdComparison: tasksUserId SPACE stringComparison # tasksUserIdBasic | tasksUserId SPACE inListStringComparison # tasksUserIdList + | tasksUserId SPACE nullComparison # tasksUserIdNull ; // basic comparisons objectIdComparison: (NOT SPACE?)? op=(EQ | NEQ) SPACE (STRING | LOGGED_USER_ID) ; stringComparison: (NOT SPACE?)? op=(EQ | NEQ | CONTAINS | LT | GT | LTE | GTE) SPACE (STRING | loggedUserStringAttribute) ; +stringLikeComparison: stringComparison LIKE ; 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 | LOGGED_USER_ANONYMOUS) ; +nullComparison: (NOT SPACE?)? op=(EQ | NEQ) SPACE NULL ; // in list/in range comparisons inListStringComparison: (NOT SPACE?)? op=IN SPACE? stringList ; @@ -277,7 +302,7 @@ LT: L T | '<' ; GT: G T | '>' ; LTE: L T E | '<=' ; GTE: G T E | '>=' ; -CONTAINS: C O N T A I N S | '~'; +CONTAINS: C O N T A I N S | '~' ; IN: I N ; // resurces @@ -346,6 +371,8 @@ DATETIME: DATE 'T' ([01] DIGIT | '2' [0-3]) ':' [0-5] DIGIT ':' [0-5] DIGIT ('.' 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+ ; +NULL: N U L L ; +LIKE: '*' ; JAVA_ID: [a-zA-Z$_] [a-zA-Z0-9$_]* ; SPACE: [ ]+ ; 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 index acb085c3933..00394b679b1 100644 --- 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 @@ -7,5 +7,7 @@ public enum ComparisonType { DATE, DATETIME, BOOLEAN, - OPTIONS + OPTIONS, + NULL, + LIKE } diff --git a/src/main/java/com/netgrif/application/engine/pfql/service/QueryLangEvaluator.java b/src/main/java/com/netgrif/application/engine/pfql/service/QueryLangEvaluator.java index cb55327ffd5..9b8a3971421 100644 --- a/src/main/java/com/netgrif/application/engine/pfql/service/QueryLangEvaluator.java +++ b/src/main/java/com/netgrif/application/engine/pfql/service/QueryLangEvaluator.java @@ -40,6 +40,7 @@ public class QueryLangEvaluator extends QueryLangBaseListener { private final ParseTreeProperty elasticQuery = new ParseTreeProperty<>(); private final ParseTreeProperty mongoQuery = new ParseTreeProperty<>(); + private final String elasticFuzzyMaxDistance = "AUTO"; private final IUserService userService; @@ -537,6 +538,7 @@ public void exitIdBasic(QueryLangParser.IdBasicContext ctx) { break; case TASK: qObjectId = QTask.task._id; + setElasticQuery(ctx, buildElasticQuery("stringId", op.getType(), objectId.toString(), not)); break; case USER: qObjectId = QUser.user._id; @@ -555,6 +557,7 @@ public void exitIdList(QueryLangParser.IdListContext ctx) { boolean not = ctx.inListStringComparison().NOT() != null; checkOp(ComparisonType.ID, op); List objectIdList = handleObjectIdListComparison(ctx.inListStringComparison().stringList()); + List stringIdList = objectIdList.stream().map(ObjectId::toString).collect(Collectors.toList()); switch (resourceType) { case PROCESS: @@ -562,11 +565,11 @@ public void exitIdList(QueryLangParser.IdListContext ctx) { break; case CASE: qObjectId = QCase.case$._id; - List stringIdList = objectIdList.stream().map(ObjectId::toString).collect(Collectors.toList()); setElasticQuery(ctx, buildElasticQueryInList("stringId", stringIdList, not)); break; case TASK: qObjectId = QTask.task._id; + setElasticQuery(ctx, buildElasticQueryInList("stringId", stringIdList, not)); break; case USER: qObjectId = QUser.user._id; @@ -584,6 +587,10 @@ public void exitTitleBasic(QueryLangParser.TitleBasicContext ctx) { Token op = ctx.stringComparison().op; boolean not = ctx.stringComparison().NOT() != null; String string = handleStringComparisonWithPlaceholders(ctx.stringComparison()); + String elasticAttribute = "title"; + if (op.getType() == QueryLangParser.EQ || op.getType() == QueryLangParser.NEQ) { + elasticAttribute += ".keyword"; + } switch (resourceType) { case PROCESS: @@ -591,10 +598,11 @@ public void exitTitleBasic(QueryLangParser.TitleBasicContext ctx) { break; case CASE: stringPath = QCase.case$.title; - setElasticQuery(ctx, buildElasticQuery("title", op.getType(), string, not)); + setElasticQuery(ctx, buildElasticQuery(elasticAttribute, op.getType(), string, not)); break; case TASK: stringPath = QTask.task.title.defaultValue; + setElasticQuery(ctx, buildElasticQuery(elasticAttribute, op.getType(), string, not)); break; default: throw new IllegalArgumentException("Unknown query type: " + resourceType); @@ -619,6 +627,7 @@ public void exitTitleList(QueryLangParser.TitleListContext ctx) { break; case TASK: stringPath = QTask.task.title.defaultValue; + setElasticQuery(ctx, buildElasticQueryInList("title", stringList, not)); break; default: throw new IllegalArgumentException("Unknown query type: " + resourceType); @@ -646,6 +655,8 @@ public void exitTitleRange(QueryLangParser.TitleRangeContext ctx) { break; case TASK: stringPath = QTask.task.title.defaultValue; + setElasticQuery(ctx, buildElasticQueryInRange("title", leftAndRightStrings.getFirst(), + leftEndpointOpen, leftAndRightStrings.getSecond(), rightEndpointOpen, not)); break; default: throw new IllegalArgumentException("Unknown query type: " + resourceType); @@ -828,6 +839,7 @@ public void exitProcessIdBasic(QueryLangParser.ProcessIdBasicContext ctx) { String string = handleStringComparisonWithPlaceholders(ctx.stringComparison()); setMongoQuery(ctx, buildStringPredicate(stringPath, op.getType(), string, not)); + setElasticQuery(ctx, buildElasticQuery("processId", op.getType(), string, not)); } @Override @@ -837,6 +849,7 @@ public void exitProcessIdList(QueryLangParser.ProcessIdListContext ctx) { List stringList = handleStringListComparison(ctx.inListStringComparison().stringList()); setMongoQuery(ctx, buildStringPredicateInList(stringPath, stringList, not)); + setElasticQuery(ctx, buildElasticQueryInList("processId", stringList, not)); } @Override @@ -925,6 +938,7 @@ public void exitTransitionIdBasic(QueryLangParser.TransitionIdBasicContext ctx) String string = handleStringComparisonWithPlaceholders(ctx.stringComparison()); setMongoQuery(ctx, buildStringPredicate(stringPath, op.getType(), string, not)); + setElasticQuery(ctx, buildElasticQuery("transitionId", op.getType(), string, not)); } @Override @@ -934,6 +948,7 @@ public void exitTransitionIdList(QueryLangParser.TransitionIdListContext ctx) { List stringList = handleStringListComparison(ctx.inListStringComparison().stringList()); setMongoQuery(ctx, buildStringPredicateInList(stringPath, stringList, not)); + setElasticQuery(ctx, buildElasticQueryInList("transitionId", stringList, not)); } @Override @@ -946,6 +961,8 @@ public void exitTransitionIdRange(QueryLangParser.TransitionIdRangeContext ctx) setMongoQuery(ctx, buildStringPredicateInRange(stringPath, leftAndRightStrings.getFirst(), leftEndpointOpen, leftAndRightStrings.getSecond(), rightEndpointOpen, not)); + setElasticQuery(ctx, buildElasticQueryInRange("transitionId", leftAndRightStrings.getFirst(), + leftEndpointOpen, leftAndRightStrings.getSecond(), rightEndpointOpen, not)); } @Override @@ -969,6 +986,7 @@ public void exitUserIdBasic(QueryLangParser.UserIdBasicContext ctx) { String string = handleStringComparisonWithPlaceholders(ctx.stringComparison()); setMongoQuery(ctx, buildStringPredicate(stringPath, op.getType(), string, not)); + setElasticQuery(ctx, buildElasticQuery("userId", op.getType(), string, not)); } @Override @@ -978,6 +996,7 @@ public void exitUserIdList(QueryLangParser.UserIdListContext ctx) { List stringList = handleStringListComparison(ctx.inListStringComparison().stringList()); setMongoQuery(ctx, buildStringPredicateInList(stringPath, stringList, not)); + setElasticQuery(ctx, buildElasticQueryInList("userId", stringList, not)); } @Override @@ -988,6 +1007,7 @@ public void exitCaseIdBasic(QueryLangParser.CaseIdBasicContext ctx) { String string = handleStringComparisonWithPlaceholders(ctx.stringComparison()); setMongoQuery(ctx, buildStringPredicate(stringPath, op.getType(), string, not)); + setElasticQuery(ctx, buildElasticQuery("caseId", op.getType(), string, not)); } @Override @@ -997,6 +1017,7 @@ public void exitCaseIdList(QueryLangParser.CaseIdListContext ctx) { List stringList = handleStringListComparison(ctx.inListStringComparison().stringList()); setMongoQuery(ctx, buildStringPredicateInList(stringPath, stringList, not)); + setElasticQuery(ctx, buildElasticQueryInList("caseId", stringList, not)); } @Override @@ -1461,7 +1482,8 @@ public void exitPlacesRange(QueryLangParser.PlacesRangeContext ctx) { } setMongoQuery(ctx, null); - setElasticQuery(ctx, buildElasticQueryInRange("places." + placeId + ".marking", leftNumberAsString, leftEndpointOpen, rightNumberAsString, rightEndpointOpen, not)); + setElasticQuery(ctx, buildElasticQueryInRange("places." + placeId + ".marking", leftNumberAsString, + leftEndpointOpen, rightNumberAsString, rightEndpointOpen, not)); this.searchWithElastic = true; } @@ -1574,4 +1596,299 @@ public void exitUserSorting(QueryLangParser.UserSortingContext ctx) { sortOrders.add(new Sort.Order(dir, prop)); }); } + + @Override + public void exitIdNull(QueryLangParser.IdNullContext ctx) { + Predicate mongoQuery; + Token op = ctx.nullComparison().op; + boolean isNotNull = shouldBeNotNull(ctx.nullComparison()); + checkOp(ComparisonType.NULL, op); + + switch (resourceType) { + case PROCESS: + mongoQuery = isNotNull ? QPetriNet.petriNet._id.isNotNull() : QPetriNet.petriNet._id.isNull(); + break; + case CASE: + mongoQuery = isNotNull ? QCase.case$._id.isNotNull() : QCase.case$._id.isNull(); + setElasticQuery(ctx, isNotNull ? "_exists_:stringId" : "!(_exists_:stringId)"); + break; + case TASK: + mongoQuery = isNotNull ? QTask.task._id.isNotNull() : QTask.task._id.isNull(); + setElasticQuery(ctx, isNotNull ? "_exists_:stringId" : "!(_exists_:stringId)"); + break; + case USER: + mongoQuery = isNotNull ? QUser.user._id.isNotNull() : QUser.user._id.isNull(); + break; + default: + throw new IllegalArgumentException("Unknown query type: " + resourceType); + } + + setMongoQuery(ctx, mongoQuery); + } + + @Override + public void exitTitleNull(QueryLangParser.TitleNullContext ctx) { + Predicate mongoQuery; + Token op = ctx.nullComparison().op; + boolean isNotNull = shouldBeNotNull(ctx.nullComparison()); + checkOp(ComparisonType.NULL, op); + + switch (resourceType) { + case PROCESS: + mongoQuery = isNotNull ? QPetriNet.petriNet.title.isNotNull() : QPetriNet.petriNet.title.isNull(); + break; + case CASE: + mongoQuery = isNotNull ? QCase.case$.title.isNotNull() : QCase.case$.title.isNull(); + setElasticQuery(ctx, isNotNull ? "_exists_:title" : "!(_exists_:title)"); + break; + case TASK: + mongoQuery = isNotNull ? QTask.task.title.isNotNull() : QTask.task.title.isNull(); + setElasticQuery(ctx, isNotNull ? "_exists_:title" : "!(_exists_:title)"); + break; + default: + throw new IllegalArgumentException("Unknown query type: " + resourceType); + } + + setMongoQuery(ctx, mongoQuery); + } + + @Override + public void exitIdentifierNull(QueryLangParser.IdentifierNullContext ctx) { + Token op = ctx.nullComparison().op; + checkOp(ComparisonType.NULL, op); + boolean isNotNull = shouldBeNotNull(ctx.nullComparison()); + setMongoQuery(ctx, isNotNull ? QPetriNet.petriNet.identifier.isNotNull() : QPetriNet.petriNet.identifier.isNull()); + } + + @Override + public void exitVersionNull(QueryLangParser.VersionNullContext ctx) { + Token op = ctx.nullComparison().op; + checkOp(ComparisonType.NULL, op); + boolean isNotNull = shouldBeNotNull(ctx.nullComparison()); + setMongoQuery(ctx, isNotNull ? QPetriNet.petriNet.version.isNotNull() : QPetriNet.petriNet.version.isNull()); + } + + @Override + public void exitCdNull(QueryLangParser.CdNullContext ctx) { + Token op = ctx.nullComparison().op; + checkOp(ComparisonType.NULL, op); + boolean isNotNull = shouldBeNotNull(ctx.nullComparison()); + + Predicate mongoQuery; + switch (resourceType) { + case PROCESS: + mongoQuery = isNotNull ? QPetriNet.petriNet.creationDate.isNotNull() : QPetriNet.petriNet.creationDate.isNull(); + break; + case CASE: + mongoQuery = isNotNull ? QCase.case$.creationDate.isNotNull() : QCase.case$.creationDate.isNull(); + setElasticQuery(ctx, isNotNull ? "_exists_:creationDateSortable" : "!(_exists_:creationDateSortable)"); + break; + default: + throw new IllegalArgumentException("Unknown query type: " + resourceType); + } + + setMongoQuery(ctx, mongoQuery); + } + + @Override + public void exitProcessIdNull(QueryLangParser.ProcessIdNullContext ctx) { + Token op = ctx.nullComparison().op; + checkOp(ComparisonType.NULL, op); + boolean isNotNull = shouldBeNotNull(ctx.nullComparison()); + setMongoQuery(ctx, isNotNull ? QTask.task.processId.isNotNull() : QTask.task.processId.isNull() ); + setElasticQuery(ctx, isNotNull ? "_exists_:processId" : "!(_exists_:processId)"); + } + + @Override + public void exitProcessIdObjNull(QueryLangParser.ProcessIdObjNullContext ctx) { + Token op = ctx.nullComparison().op; + checkOp(ComparisonType.NULL, op); + boolean isNotNull = shouldBeNotNull(ctx.nullComparison()); + setMongoQuery(ctx, isNotNull ? QCase.case$.petriNetObjectId.isNotNull() : QCase.case$.petriNetObjectId.isNull()); + setElasticQuery(ctx, isNotNull ? "_exists_:processId" : "!(_exists_:processId)"); + } + + @Override + public void exitProcessIdentifierNull(QueryLangParser.ProcessIdentifierNullContext ctx) { + Token op = ctx.nullComparison().op; + checkOp(ComparisonType.NULL, op); + boolean isNotNull = shouldBeNotNull(ctx.nullComparison()); + setMongoQuery(ctx, isNotNull ? QCase.case$.processIdentifier.isNotNull() : QCase.case$.processIdentifier.isNull()); + setElasticQuery(ctx, isNotNull ? "_exists_:processIdentifier" : "!(_exists_:processIdentifier)"); + } + + @Override + public void exitAuthorNull(QueryLangParser.AuthorNullContext ctx) { + Token op = ctx.nullComparison().op; + checkOp(ComparisonType.NULL, op); + boolean isNotNull = shouldBeNotNull(ctx.nullComparison()); + setMongoQuery(ctx, isNotNull ? QCase.case$.author.id.isNotNull() : QCase.case$.author.id.isNull()); + setElasticQuery(ctx, isNotNull ? "_exists_:author" : "!(_exists_:author)"); + } + + @Override + public void exitTransitionIdNull(QueryLangParser.TransitionIdNullContext ctx) { + Token op = ctx.nullComparison().op; + checkOp(ComparisonType.NULL, op); + boolean isNotNull = shouldBeNotNull(ctx.nullComparison()); + setMongoQuery(ctx, isNotNull ? QTask.task.transitionId.isNotNull() : QTask.task.transitionId.isNull()); + setElasticQuery(ctx, isNotNull ? "_exists_:transitionId" : "!(_exists_:transitionId)"); + } + + @Override + public void exitUserIdNull(QueryLangParser.UserIdNullContext ctx) { + Token op = ctx.nullComparison().op; + checkOp(ComparisonType.NULL, op); + boolean isNotNull = shouldBeNotNull(ctx.nullComparison()); + setMongoQuery(ctx, isNotNull ? QTask.task.userId.isNotNull() : QTask.task.userId.isNull()); + setElasticQuery(ctx, isNotNull ? "_exists_:userId" : "!(_exists_:userId)"); + } + + @Override + public void exitLfNull(QueryLangParser.LfNullContext ctx) { + // todo implement lastFinished + } + + @Override + public void exitCaseIdNull(QueryLangParser.CaseIdNullContext ctx) { + Token op = ctx.nullComparison().op; + checkOp(ComparisonType.NULL, op); + boolean isNotNull = shouldBeNotNull(ctx.nullComparison()); + setMongoQuery(ctx, isNotNull ? QTask.task.caseId.isNotNull() : QTask.task.caseId.isNull()); + setElasticQuery(ctx, isNotNull ? "_exists_:caseId" : "!(_exists_:caseId)"); + } + + @Override + public void exitLaNull(QueryLangParser.LaNullContext ctx) { + // todo implement lastAssigned + } + + @Override + public void exitNameNull(QueryLangParser.NameNullContext ctx) { + Token op = ctx.nullComparison().op; + checkOp(ComparisonType.NULL, op); + boolean isNotNull = shouldBeNotNull(ctx.nullComparison()); + setMongoQuery(ctx, isNotNull ? QUser.user.name.isNotNull() : QUser.user.name.isNull()); + } + + @Override + public void exitSurnameNull(QueryLangParser.SurnameNullContext ctx) { + Token op = ctx.nullComparison().op; + checkOp(ComparisonType.NULL, op); + boolean isNotNull = shouldBeNotNull(ctx.nullComparison()); + setMongoQuery(ctx, isNotNull ? QUser.user.surname.isNotNull() : QUser.user.surname.isNull()); + } + + @Override + public void exitEmailNull(QueryLangParser.EmailNullContext ctx) { + Token op = ctx.nullComparison().op; + checkOp(ComparisonType.NULL, op); + boolean isNotNull = shouldBeNotNull(ctx.nullComparison()); + setMongoQuery(ctx, isNotNull ? QUser.user.email.isNotNull() : QUser.user.email.isNull()); + } + + @Override + public void exitDataNull(QueryLangParser.DataNullContext ctx) { + Token op = ctx.nullComparison().op; + checkOp(ComparisonType.NULL, op); + boolean isNotNull = shouldBeNotNull(ctx.nullComparison()); + String fieldId = ctx.dataValue().fieldId.getText(); + String elasticAttribute = "dataSet." + fieldId + ".fulltextValue"; + + setMongoQuery(ctx, null); + setElasticQuery(ctx, isNotNull ? "_exists_:" + elasticAttribute : "!(_exists_:" + elasticAttribute + ")"); + this.searchWithElastic = true; + } + + @Override + public void exitDataOptionsNull(QueryLangParser.DataOptionsNullContext ctx) { + Token op = ctx.nullComparison().op; + checkOp(ComparisonType.NULL, op); + boolean isNotNull = shouldBeNotNull(ctx.nullComparison()); + String fieldId = ctx.dataOptions().fieldId.getText(); + String elasticAttribute = "dataSet." + fieldId + ".options"; + + setMongoQuery(ctx, null); + setElasticQuery(ctx, isNotNull ? "_exists_:" + elasticAttribute : "!(_exists_:" + elasticAttribute + ")"); + this.searchWithElastic = true; + } + + @Override + public void exitPlacesNull(QueryLangParser.PlacesNullContext ctx) { + Token op = ctx.nullComparison().op; + checkOp(ComparisonType.NULL, op); + boolean isNotNull = shouldBeNotNull(ctx.nullComparison()); + String placeId = ctx.places().placeId.getText(); + String elasticAttribute = "places." + placeId + ".marking"; + + setMongoQuery(ctx, null); + setElasticQuery(ctx, isNotNull ? "_exists_:" + elasticAttribute : "!(_exists_:" + elasticAttribute + ")"); + this.searchWithElastic = true; + } + + @Override + public void exitTasksUserIdNull(QueryLangParser.TasksUserIdNullContext ctx) { + Token op = ctx.nullComparison().op; + checkOp(ComparisonType.NULL, op); + boolean isNotNull = shouldBeNotNull(ctx.nullComparison()); + String taskId = ctx.tasksUserId().taskId.getText(); + String elasticAttribute = "tasks." + taskId + ".userId"; + + setMongoQuery(ctx, null); + setElasticQuery(ctx, isNotNull ? "_exists_:" + elasticAttribute : "!(_exists_:" + elasticAttribute + ")"); + this.searchWithElastic = true; + } + + @Override + public void exitTitleLike(QueryLangParser.TitleLikeContext ctx) { + StringPath stringPath; + Token op = ctx.stringLikeComparison().stringComparison().op; + checkOp(ComparisonType.LIKE, op); + boolean not = ctx.stringLikeComparison().stringComparison().NOT() != null; + String string = handleStringComparisonWithPlaceholders(ctx.stringLikeComparison().stringComparison()); + + switch (resourceType) { + case PROCESS: + stringPath = QPetriNet.petriNet.title.defaultValue; + break; + case CASE: + stringPath = QCase.case$.title; + setElasticQuery(ctx, buildElasticQuery("title", op.getType(), string + "~" + elasticFuzzyMaxDistance, not)); + break; + case TASK: + stringPath = QTask.task.title.defaultValue; + setElasticQuery(ctx, buildElasticQuery("title", op.getType(), string + "~" + elasticFuzzyMaxDistance, not)); + break; + default: + throw new IllegalArgumentException("Unknown query type: " + resourceType); + } + + boolean negate = (op.getType() == QueryLangParser.NEQ) != not; + Predicate mongoQuery = stringPath.likeIgnoreCase("%" + string + "%"); + setMongoQuery(ctx, negate ? mongoQuery.not() : mongoQuery); + } + + @Override + public void exitDataStringLike(QueryLangParser.DataStringLikeContext ctx) { + String fieldId = ctx.dataValue().fieldId.getText(); + Token op = ctx.stringLikeComparison().stringComparison().op; + checkOp(ComparisonType.LIKE, op); + boolean not = ctx.stringLikeComparison().stringComparison().NOT() != null; + String string = handleStringComparisonWithPlaceholders(ctx.stringLikeComparison().stringComparison()); + + setMongoQuery(ctx, null); + setElasticQuery(ctx, buildElasticQuery("dataSet." + fieldId + ".fulltextValue", op.getType(), + string + "~" + elasticFuzzyMaxDistance, not)); + this.searchWithElastic = true; + } + + private boolean shouldBeNotNull(QueryLangParser.NullComparisonContext ctx) { + if (ctx == null) { + throw new IllegalArgumentException("Null comparison context must be provided"); + } + if (ctx.EQ() == null && ctx.NEQ() == null) { + throw new IllegalArgumentException("Any of the operators EQ or NEQ must be used"); + } + return ctx.NOT() != null && ctx.EQ() != null || ctx.NOT() == null && ctx.NEQ() != null; + } } diff --git a/src/main/java/com/netgrif/application/engine/pfql/service/utils/SearchUtils.java b/src/main/java/com/netgrif/application/engine/pfql/service/utils/SearchUtils.java index b838499225d..b7b01c5fed5 100644 --- a/src/main/java/com/netgrif/application/engine/pfql/service/utils/SearchUtils.java +++ b/src/main/java/com/netgrif/application/engine/pfql/service/utils/SearchUtils.java @@ -2,6 +2,7 @@ import com.netgrif.application.engine.auth.service.interfaces.IUserService; import com.netgrif.application.engine.configuration.ApplicationContextProvider; +import com.netgrif.application.engine.elastic.service.ElasticsearchQuerySanitizer; 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; @@ -45,7 +46,9 @@ public class SearchUtils { 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) + ComparisonType.BOOLEAN, List.of(QueryLangParser.EQ, QueryLangParser.NEQ), + ComparisonType.NULL, List.of(QueryLangParser.EQ, QueryLangParser.NEQ), + ComparisonType.LIKE, List.of(QueryLangParser.EQ, QueryLangParser.NEQ) ); public static final Map processAttrToSortPropMapping = Map.of( @@ -95,6 +98,7 @@ public class SearchUtils { public static final String LEFT_OPEN_ENDPOINT = "("; public static final String RIGHT_OPEN_ENDPOINT = ")"; + protected static final String[] ELASTIC_EXCLUDE_FROM_ESCAPING = new String[]{"~", " "}; public static String toDateString(LocalDate localDate) { return localDate.format(DateTimeFormatter.ISO_LOCAL_DATE); @@ -402,6 +406,28 @@ public static Predicate buildDateTimePredicateInRange(DateTimePath values, boolean not) { + values = quoteAndSanitizeForElastic(values); + String valuesQuery = "(" + String.join(" OR ", values) + ")"; + return doBuildElasticQuery(attribute, QueryLangParser.IN, valuesQuery, not); + } + + public static String buildElasticQueryInRange(String attribute, String leftValue, boolean leftEndpointOpen, String rightValue, boolean rightEndpointOpen, boolean not) { + leftValue = quoteAndSanitizeForElastic(leftValue); + rightValue = quoteAndSanitizeForElastic(rightValue); + String query = "(" + + doBuildElasticQuery(attribute, leftEndpointOpen ? QueryLangParser.GT : QueryLangParser.GTE, leftValue, false) + + " AND " + + doBuildElasticQuery(attribute, rightEndpointOpen ? QueryLangParser.LT : QueryLangParser.LTE, rightValue, false) + + ")"; + return not ? "NOT " + query : query; + } + + protected static String doBuildElasticQuery(String attribute, int op, String value, boolean not) { String query = null; switch (op) { case QueryLangParser.EQ: @@ -439,17 +465,39 @@ public static String buildElasticQuery(String attribute, int op, String value, b 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); + protected static List quoteAndSanitizeForElastic(List values) { + return values.stream().map(SearchUtils::quoteAndSanitizeForElastic).collect(Collectors.toList()); } - 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; + /** + * Adds quotes for value that is going to be used in the Elasticsearch query. If the value does not contain a fuzzy symbol, + * origin value wrapped in quotes is returned. For example, `someValue anotherValue` -> `"someValue anotherValue"`. If the value + * contains a fuzzy symbol, it places the fuzzy symbol after every term. For example, `someVxlue anotherVxlue~AUTO` -> + * `(someVxlue~AUTO AND anotherVxlue~AUTO)`. The originValue is also sanitized using {@link ElasticsearchQuerySanitizer#sanitize(String, String[])}. + */ + protected static String quoteAndSanitizeForElastic(String originValue) { + originValue = ElasticsearchQuerySanitizer.sanitize(originValue, ELASTIC_EXCLUDE_FROM_ESCAPING); + if (originValue == null || (!containsWhitespace(originValue) && !originValue.isEmpty())) { + return originValue; + } + + int fuzzyIndex = originValue.indexOf('~'); + return fuzzyIndex != -1 ? resolvePhraseWithFuzzy(originValue, fuzzyIndex) : "\"" + originValue + "\""; + } + + protected static String resolvePhraseWithFuzzy(String originPhraseWithFuzzy, int fuzzyIndex) { + String fuzzy = originPhraseWithFuzzy.substring(fuzzyIndex); + String originPhraseWithoutFuzzy = originPhraseWithFuzzy.substring(0, fuzzyIndex); + String[] splitPhrase = originPhraseWithoutFuzzy.trim().split("\\s+"); + return "(" + String.join(fuzzy + " AND ", splitPhrase) + fuzzy + ")"; + } + + protected static boolean containsWhitespace(String value) { + for (int i = 0; i < value.length(); i++) { + if (Character.isWhitespace(value.charAt(i))) { + return true; + } + } + return false; } } diff --git a/src/main/java/com/netgrif/application/engine/workflow/domain/filter/Configuration.java b/src/main/java/com/netgrif/application/engine/workflow/domain/filter/Configuration.java deleted file mode 100644 index 00b190e0171..00000000000 --- a/src/main/java/com/netgrif/application/engine/workflow/domain/filter/Configuration.java +++ /dev/null @@ -1,48 +0,0 @@ -package com.netgrif.application.engine.workflow.domain.filter; - -import com.fasterxml.jackson.annotation.JsonIgnore; -import lombok.EqualsAndHashCode; -import lombok.Getter; -import lombok.NoArgsConstructor; -import lombok.Setter; - -import java.util.HashMap; -import java.util.Map; - -/** - * Class represents configuration part on predicate. - * Class holds operator, which provides operation for value comparison. - * When creating filter for datafield value, datafield attribute is also used, - * to represent which datafield from which process is used in comparison. - */ -@EqualsAndHashCode -@NoArgsConstructor -@Getter -@Setter -public class Configuration { - protected String operator; - protected String datafield; - - public Configuration(Map value) { - value.forEach((k, v) -> { - switch (k) { - case "operator": - operator = (String) v; - break; - case "datafield": - datafield = (String) v; - break; - } - }); - } - - @JsonIgnore - public Map getMapObject() { - Map mapObject = new HashMap<>(); - mapObject.put("operator", operator); - if (datafield != null) { - mapObject.put("datafield", datafield); - } - return mapObject; - } -} diff --git a/src/main/java/com/netgrif/application/engine/workflow/domain/filter/DoubleValueHolder.java b/src/main/java/com/netgrif/application/engine/workflow/domain/filter/DoubleValueHolder.java deleted file mode 100644 index 47633a6343a..00000000000 --- a/src/main/java/com/netgrif/application/engine/workflow/domain/filter/DoubleValueHolder.java +++ /dev/null @@ -1,15 +0,0 @@ -package com.netgrif.application.engine.workflow.domain.filter; - -public abstract class DoubleValueHolder { - protected Double convertObjectToDouble(Object val) { - if (val instanceof Double) - return (Double) val; - else if (val instanceof Integer) - return new Double((Integer) val); - else if (val instanceof Float) - return new Double((Float) val); - else if (val instanceof String) - return Double.parseDouble((String) val); - throw new IllegalArgumentException("The provided Object (" + val.toString() + ") cannot be converted to Double"); - } -} diff --git a/src/main/java/com/netgrif/application/engine/workflow/domain/filter/FilterImportExport.java b/src/main/java/com/netgrif/application/engine/workflow/domain/filter/FilterImportExport.java deleted file mode 100644 index edb11fbc677..00000000000 --- a/src/main/java/com/netgrif/application/engine/workflow/domain/filter/FilterImportExport.java +++ /dev/null @@ -1,73 +0,0 @@ -package com.netgrif.application.engine.workflow.domain.filter; - -import com.fasterxml.jackson.dataformat.xml.annotation.JacksonXmlElementWrapper; -import com.fasterxml.jackson.dataformat.xml.annotation.JacksonXmlProperty; -import com.netgrif.application.engine.petrinet.domain.I18nString; -import lombok.EqualsAndHashCode; -import lombok.Getter; -import lombok.NoArgsConstructor; -import lombok.Setter; - -import java.util.List; -import java.util.Map; -import java.util.Objects; - -/** - * Class that represents one exported filter. - * This class holds all information about one filter, so filter can be fully build from this class. - * This class is represented by tag in exported xml file. - */ -@EqualsAndHashCode -@NoArgsConstructor -@Getter -@Setter -public class FilterImportExport { - @EqualsAndHashCode.Exclude - protected String caseId; - @EqualsAndHashCode.Exclude - protected String parentCaseId; - protected String parentViewId; - protected I18nString filterName; - protected String filterValue; - protected String visibility; - protected String type; - protected String icon; - @JacksonXmlElementWrapper(localName = "allowedNets") - @JacksonXmlProperty(localName = "allowedNet") - protected List allowedNets; - @JacksonXmlProperty(localName = "filterMetadata") - protected FilterMetadataExport filterMetadataExport; - - public void setFilterMetadataExport(Map value) { - this.filterMetadataExport = new FilterMetadataExport(value); - } - - @Override - public boolean equals(Object o) { - if (this == o) return true; - if (o == null || getClass() != o.getClass()) return false; - - FilterImportExport that = (FilterImportExport) o; - - if (!Objects.equals(parentViewId, that.parentViewId)) return false; - if (!Objects.equals(filterName, that.filterName)) return false; - if (!Objects.equals(filterValue, that.filterValue)) return false; - if (!Objects.equals(visibility, that.visibility)) return false; - if (!Objects.equals(type, that.type)) return false; - if (!Objects.equals(allowedNets, that.allowedNets)) return false; - return Objects.equals(filterMetadataExport, that.filterMetadataExport); - } - - @Override - public int hashCode() { - int result = parentViewId != null ? parentViewId.hashCode() : 0; - result = 31 * result + (filterName != null ? filterName.hashCode() : 0); - result = 31 * result + (filterValue != null ? filterValue.hashCode() : 0); - result = 31 * result + (visibility != null ? visibility.hashCode() : 0); - result = 31 * result + (type != null ? type.hashCode() : 0); - result = 31 * result + (icon != null ? icon.hashCode() : 0); - result = 31 * result + (allowedNets != null ? allowedNets.hashCode() : 0); - result = 31 * result + (filterMetadataExport != null ? filterMetadataExport.hashCode() : 0); - return result; - } -} diff --git a/src/main/java/com/netgrif/application/engine/workflow/domain/filter/FilterImportExportList.java b/src/main/java/com/netgrif/application/engine/workflow/domain/filter/FilterImportExportList.java deleted file mode 100644 index 36551dc496e..00000000000 --- a/src/main/java/com/netgrif/application/engine/workflow/domain/filter/FilterImportExportList.java +++ /dev/null @@ -1,49 +0,0 @@ -package com.netgrif.application.engine.workflow.domain.filter; - -import com.fasterxml.jackson.dataformat.xml.annotation.JacksonXmlElementWrapper; -import com.fasterxml.jackson.dataformat.xml.annotation.JacksonXmlProperty; -import com.fasterxml.jackson.dataformat.xml.annotation.JacksonXmlRootElement; -import lombok.EqualsAndHashCode; -import lombok.Getter; -import lombok.Setter; - -import java.util.ArrayList; -import java.util.List; -import java.util.Objects; - -/** - * Class that wraps and holds list of filters, which are meant to be exported/imported. - * This is the root class of xml file, that is created after exporting of filters. - * The root tag of the xml file is: - * tag is followed by list of tags. - * Whole schema for the xml file is on the path: resources/petriNets/filter_export_schema.xsd - */ -@EqualsAndHashCode -@Getter -@Setter -@JacksonXmlRootElement(localName = "filters") -public class FilterImportExportList { - - @JacksonXmlElementWrapper(useWrapping = false) - @JacksonXmlProperty(localName = "filter") - protected List filters; - - public FilterImportExportList() { - this.filters = new ArrayList<>(); - } - - @Override - public boolean equals(Object o) { - if (this == o) return true; - if (o == null || getClass() != o.getClass()) return false; - - FilterImportExportList that = (FilterImportExportList) o; - - return Objects.equals(filters, that.filters); - } - - @Override - public int hashCode() { - return filters != null ? filters.hashCode() : 0; - } -} \ No newline at end of file diff --git a/src/main/java/com/netgrif/application/engine/workflow/domain/filter/FilterMetadataExport.java b/src/main/java/com/netgrif/application/engine/workflow/domain/filter/FilterMetadataExport.java deleted file mode 100644 index d06229e3700..00000000000 --- a/src/main/java/com/netgrif/application/engine/workflow/domain/filter/FilterMetadataExport.java +++ /dev/null @@ -1,104 +0,0 @@ -package com.netgrif.application.engine.workflow.domain.filter; - -import com.fasterxml.jackson.annotation.JsonIgnore; -import com.fasterxml.jackson.dataformat.xml.annotation.JacksonXmlElementWrapper; -import com.fasterxml.jackson.dataformat.xml.annotation.JacksonXmlProperty; -import lombok.EqualsAndHashCode; -import lombok.Getter; -import lombok.NoArgsConstructor; -import lombok.Setter; - -import java.util.*; - -/** - * This class represents complex structure of filter field metadata object. - * This class is represented by tag in xml document. - * While exporting, class is created from complex structure (map) which consists of: - * keys as strings - * values as objects - * This structure needs to be recreated when importing filter by method getMapObject(). - */ -@EqualsAndHashCode -@NoArgsConstructor -@Getter -@Setter -public class FilterMetadataExport { - protected String filterType; - protected boolean defaultSearchCategories; - protected boolean inheritAllowedNets; - @JacksonXmlElementWrapper(localName = "searchCategories") - @JacksonXmlProperty(localName = "searchCategory") - protected List searchCategories; - @JacksonXmlElementWrapper(localName = "predicateMetadata") - @JacksonXmlProperty(localName = "predicateMetadataItem") - protected List predicateMetadata; - - public FilterMetadataExport(Map value) { - value.forEach((k, v) -> { - switch (k) { - case "filterType": - filterType = (String) v; - break; - case "defaultSearchCategories": - defaultSearchCategories = String.valueOf(v).equals("true"); - break; - case "inheritAllowedNets": - inheritAllowedNets = String.valueOf(v).equals("true"); - break; - case "searchCategories": - searchCategories = (List) v; - break; - case "predicateMetadata": - predicateMetadata = new ArrayList<>(); - List list = (List) v; - for (Object val : list) { - if (val instanceof List) { - predicateMetadata.add(new PredicateArray((List) val)); - } else { - predicateMetadata.add(new PredicateArray((Map) val)); - } - } - break; - } - }); - } - - @JsonIgnore - public Map getMapObject() { - Map mapObject = new HashMap<>(); - List listPredicateMetadata = new ArrayList<>(); - if (predicateMetadata != null) { - for (PredicateArray val : predicateMetadata) { - listPredicateMetadata.add(val.getMapObject()); - } - } - mapObject.put("predicateMetadata", listPredicateMetadata); - mapObject.put("searchCategories", searchCategories != null ? searchCategories : new ArrayList()); - return mapObject; - } - - @Override - public boolean equals(Object o) { - if (this == o) return true; - if (o == null || getClass() != o.getClass()) return false; - - FilterMetadataExport that = (FilterMetadataExport) o; - - if (defaultSearchCategories != that.defaultSearchCategories) return false; - if (inheritAllowedNets != that.inheritAllowedNets) return false; - if (!Objects.equals(filterType, that.filterType)) return false; - if (!Objects.equals(searchCategories, that.searchCategories)) - return false; - return Objects.equals(predicateMetadata, that.predicateMetadata); - } - - @Override - public int hashCode() { - int result = filterType != null ? filterType.hashCode() : 0; - result = 31 * result + (defaultSearchCategories ? 1 : 0); - result = 31 * result + (inheritAllowedNets ? 1 : 0); - result = 31 * result + (searchCategories != null ? searchCategories.hashCode() : 0); - result = 31 * result + (predicateMetadata != null ? predicateMetadata.hashCode() : 0); - return result; - } -} diff --git a/src/main/java/com/netgrif/application/engine/workflow/domain/filter/Predicate.java b/src/main/java/com/netgrif/application/engine/workflow/domain/filter/Predicate.java deleted file mode 100644 index af4319187e1..00000000000 --- a/src/main/java/com/netgrif/application/engine/workflow/domain/filter/Predicate.java +++ /dev/null @@ -1,157 +0,0 @@ -package com.netgrif.application.engine.workflow.domain.filter; - -import com.fasterxml.jackson.annotation.JsonIgnore; -import com.fasterxml.jackson.dataformat.xml.annotation.JacksonXmlElementWrapper; -import com.fasterxml.jackson.dataformat.xml.annotation.JacksonXmlProperty; -import lombok.EqualsAndHashCode; -import lombok.Getter; -import lombok.NoArgsConstructor; -import lombok.Setter; - -import java.util.ArrayList; -import java.util.HashMap; -import java.util.List; -import java.util.Map; - -/** - * Predicate class represents one search predicate (search term). - * In the xml document, this class is represented with tag. - * Depending on search category and configuration, there could be 5 different - * types of values. - * Same as the PredicateArray class, this one needs to be converted into map object - * when importing filter. - */ -@EqualsAndHashCode -@NoArgsConstructor -@Getter -@Setter -public class Predicate extends DoubleValueHolder { - protected String category; - protected Configuration configuration; - @JacksonXmlElementWrapper(localName = "stringValues") - @JacksonXmlProperty(localName = "stringValue") - protected List stringValues; - @JacksonXmlElementWrapper(localName = "doubleValues") - @JacksonXmlProperty(localName = "doubleValue") - protected List doubleValues; - @JacksonXmlElementWrapper(localName = "booleanValues") - @JacksonXmlProperty(localName = "booleanValue") - protected List booleanValues; - @JacksonXmlElementWrapper(localName = "mapValues") - @JacksonXmlProperty(localName = "mapValue") - protected List mapValues; - @JacksonXmlElementWrapper(localName = "longValues") - @JacksonXmlProperty(localName = "longValue") - protected List longValues; - - public Predicate(Map value) { - value.forEach((k, v) -> { - switch (k) { - case "category": - category = (String) v; - break; - case "configuration": - configuration = new Configuration((Map) v); - break; - case "values": - List list = (List) v; - if (list.get(0) instanceof String) { - stringValues = new ArrayList<>(); - for (Object val : list) { - stringValues.add((String) val); - } - } else if (list.get(0) instanceof Boolean) { - booleanValues = new ArrayList<>(); - for (Object val : list) { - booleanValues.add((Boolean) val); - } - } else if (list.get(0) instanceof Integer || list.get(0) instanceof Double || list.get(0) instanceof Float) { - doubleValues = new ArrayList<>(); - for (Object val : list) { - doubleValues.add(convertObjectToDouble(val)); - } - } else if (list.get(0) instanceof Long) { - longValues = new ArrayList<>(); - for (Object val : list) { - longValues.add((Long) val); - } - } else { - mapValues = new ArrayList<>(); - for (Object val : list) { - mapValues.add(new PredicateValue((Map) val)); - } - } - break; - case "stringValues": - stringValues = new ArrayList<>(); - List stringList = (List) v; - stringValues.addAll(stringList); - break; - case "doubleValues": - doubleValues = new ArrayList<>(); - for (Object val : (List) v) { - doubleValues.add(convertObjectToDouble(val)); - } - break; - case "longValues": - longValues = new ArrayList<>(); - for (Object val : (List) v) { - if (val instanceof Long) { - longValues.add((Long) val); - continue; - } else if (val instanceof String) { - longValues.add(Long.parseLong((String) val)); - continue; - } - throw new IllegalArgumentException("The provided Object (" + val.toString() + ") cannot be converted to Long"); - } - break; - case "booleanValues": - booleanValues = new ArrayList<>(); - for (Object val : (List) v) { - if (val instanceof Boolean) { - booleanValues.add((Boolean) val); - continue; - } else if (val instanceof String) { - booleanValues.add(Boolean.parseBoolean((String) val)); - continue; - } - throw new IllegalArgumentException("The provided Object (" + val.toString() + ") cannot be converted to Boolean"); - } - break; - case "mapValues": - mapValues = new ArrayList<>(); - List> mapList = (List>) v; - for (Map val : mapList) { - mapValues.add(new PredicateValue(val)); - } - break; - } - }); - } - - @JsonIgnore - public Map getMapObject() { - Map mapObject = new HashMap<>(); - mapObject.put("category", category); - mapObject.put("configuration", configuration.getMapObject()); - if (mapValues != null) { - List tmpList = new ArrayList<>(); - for (PredicateValue val : mapValues) { - tmpList.add(val.getMapObject()); - } - mapObject.put("values", tmpList); - } else { - if (stringValues != null) { - mapObject.put("values", stringValues); - } else if (doubleValues != null) { - mapObject.put("values", doubleValues); - } else if (booleanValues != null) { - mapObject.put("values", booleanValues); - } else if (longValues != null) { - mapObject.put("values", longValues); - } - } - return mapObject; - } -} diff --git a/src/main/java/com/netgrif/application/engine/workflow/domain/filter/PredicateArray.java b/src/main/java/com/netgrif/application/engine/workflow/domain/filter/PredicateArray.java deleted file mode 100644 index 6f60120d15d..00000000000 --- a/src/main/java/com/netgrif/application/engine/workflow/domain/filter/PredicateArray.java +++ /dev/null @@ -1,70 +0,0 @@ -package com.netgrif.application.engine.workflow.domain.filter; - -import com.fasterxml.jackson.annotation.JsonIgnore; -import com.fasterxml.jackson.dataformat.xml.annotation.JacksonXmlElementWrapper; -import com.fasterxml.jackson.dataformat.xml.annotation.JacksonXmlProperty; -import lombok.EqualsAndHashCode; -import lombok.Getter; -import lombok.NoArgsConstructor; -import lombok.Setter; - -import java.util.ArrayList; -import java.util.Collection; -import java.util.List; -import java.util.Map; - -/** - * This class wraps and holds list of predicates. - * In the xml structure class is represented by tag. - * Same as the FilterMetadataExport class, this one needs to be converted into - * map object while importing filter too. - */ -@EqualsAndHashCode -@NoArgsConstructor -@Getter -@Setter -public class PredicateArray { - @JacksonXmlElementWrapper(useWrapping = false) - @JacksonXmlProperty(localName = "predicate") - protected List predicates; - - public PredicateArray(List value) { - predicates = new ArrayList<>(); - for (Object val : value) { - predicates.add(new Predicate((Map) val)); - } - } - - public PredicateArray(Map value) { - predicates = new ArrayList<>(); - value.forEach((k, v) -> { - for (Object val : ((Collection) v)) { - predicates.add(new Predicate((Map) val)); - } - }); - } - - @JsonIgnore - public List getMapObject() { - List mapObject = new ArrayList<>(); - for (Predicate val : predicates) { - mapObject.add(val.getMapObject()); - } - return mapObject; - } - - @Override - public boolean equals(Object o) { - if (this == o) return true; - if (o == null || getClass() != o.getClass()) return false; - - PredicateArray that = (PredicateArray) o; - - return predicates.size() == that.predicates.size(); // TODO implements better comparison of two PredicateArrays - } - - @Override - public int hashCode() { - return predicates != null ? predicates.hashCode() : 0; - } -} diff --git a/src/main/java/com/netgrif/application/engine/workflow/domain/filter/PredicateValue.java b/src/main/java/com/netgrif/application/engine/workflow/domain/filter/PredicateValue.java deleted file mode 100644 index a67c52b8bf7..00000000000 --- a/src/main/java/com/netgrif/application/engine/workflow/domain/filter/PredicateValue.java +++ /dev/null @@ -1,82 +0,0 @@ -package com.netgrif.application.engine.workflow.domain.filter; - -import com.fasterxml.jackson.annotation.JsonIgnore; -import com.fasterxml.jackson.dataformat.xml.annotation.JacksonXmlElementWrapper; -import com.fasterxml.jackson.dataformat.xml.annotation.JacksonXmlProperty; -import lombok.EqualsAndHashCode; -import lombok.Getter; -import lombok.NoArgsConstructor; -import lombok.Setter; - -import java.util.ArrayList; -import java.util.HashMap; -import java.util.List; -import java.util.Map; - -/** - * Class holds values of some search predicates (mainly searching for author). - * Values can be integer of author id or other search predicate as a text (<>). - */ -@EqualsAndHashCode -@NoArgsConstructor -@Getter -@Setter -public class PredicateValue extends DoubleValueHolder { - - protected String text; - @JacksonXmlElementWrapper(localName = "stringValues") - @JacksonXmlProperty(localName = "stringValue") - protected List stringValues; - @JacksonXmlElementWrapper(localName = "doubleValues") - @JacksonXmlProperty(localName = "doubleValue") - protected List doubleValues; - - public PredicateValue(Map value) { - value.forEach((k, v) -> { - switch (k) { - case "text": - text = (String) v; - break; - case "value": - List list = (List) v; - if (list.get(0) instanceof String) { - stringValues = new ArrayList<>(); - for (Object val : list) { - stringValues.add((String) val); - } - } else if (list.get(0) instanceof Integer || list.get(0) instanceof Double || list.get(0) instanceof Float) { - doubleValues = new ArrayList<>(); - for (Object val : list) { - doubleValues.add(convertObjectToDouble(val)); - } - } - break; - case "stringValues": - stringValues = new ArrayList<>(); - List stringList = (List) v; - for (Object val : stringList) { - stringValues.add((String) val); - } - break; - case "doubleValues": - doubleValues = new ArrayList<>(); - for (Object val : (List) v) { - doubleValues.add(convertObjectToDouble(val)); - } - break; - } - }); - } - - @JsonIgnore - public Map getMapObject() { - Map mapObject = new HashMap<>(); - mapObject.put("text", text); - if (doubleValues != null) { - mapObject.put("value", doubleValues); - } else if (stringValues != null) { - mapObject.put("value", stringValues); - } - return mapObject; - } -} diff --git a/src/main/java/com/netgrif/application/engine/workflow/web/responsebodies/LocalisedCaseFilterField.java b/src/main/java/com/netgrif/application/engine/workflow/web/responsebodies/LocalisedCaseFilterField.java index d63e8d1d946..48d020bfe93 100644 --- a/src/main/java/com/netgrif/application/engine/workflow/web/responsebodies/LocalisedCaseFilterField.java +++ b/src/main/java/com/netgrif/application/engine/workflow/web/responsebodies/LocalisedCaseFilterField.java @@ -1,6 +1,5 @@ package com.netgrif.application.engine.workflow.web.responsebodies; -import com.netgrif.application.engine.petrinet.domain.Component; import com.netgrif.application.engine.petrinet.domain.dataset.CaseFilterField; import lombok.Data; import lombok.EqualsAndHashCode; @@ -13,6 +12,5 @@ public class LocalisedCaseFilterField extends LocalisedField { public LocalisedCaseFilterField(CaseFilterField field, Locale locale) { super(field, locale); - setComponent(new Component("string_query")); } } diff --git a/src/main/java/com/netgrif/application/engine/workflow/web/responsebodies/LocalisedProcessFilterField.java b/src/main/java/com/netgrif/application/engine/workflow/web/responsebodies/LocalisedProcessFilterField.java index 70886f8caaf..6455f65ce65 100644 --- a/src/main/java/com/netgrif/application/engine/workflow/web/responsebodies/LocalisedProcessFilterField.java +++ b/src/main/java/com/netgrif/application/engine/workflow/web/responsebodies/LocalisedProcessFilterField.java @@ -1,6 +1,5 @@ package com.netgrif.application.engine.workflow.web.responsebodies; -import com.netgrif.application.engine.petrinet.domain.Component; import com.netgrif.application.engine.petrinet.domain.dataset.ProcessFilterField; import lombok.Data; import lombok.EqualsAndHashCode; @@ -13,6 +12,5 @@ public class LocalisedProcessFilterField extends LocalisedField { public LocalisedProcessFilterField(ProcessFilterField field, Locale locale) { super(field, locale); - setComponent(new Component("string_query")); } } diff --git a/src/main/java/com/netgrif/application/engine/workflow/web/responsebodies/LocalisedTaskFilterField.java b/src/main/java/com/netgrif/application/engine/workflow/web/responsebodies/LocalisedTaskFilterField.java index 1a04a13a75a..d0909ff0e5b 100644 --- a/src/main/java/com/netgrif/application/engine/workflow/web/responsebodies/LocalisedTaskFilterField.java +++ b/src/main/java/com/netgrif/application/engine/workflow/web/responsebodies/LocalisedTaskFilterField.java @@ -1,6 +1,5 @@ package com.netgrif.application.engine.workflow.web.responsebodies; -import com.netgrif.application.engine.petrinet.domain.Component; import com.netgrif.application.engine.petrinet.domain.dataset.TaskFilterField; import lombok.Data; import lombok.EqualsAndHashCode; @@ -13,6 +12,5 @@ public class LocalisedTaskFilterField extends LocalisedField { public LocalisedTaskFilterField(TaskFilterField field, Locale locale) { super(field, locale); - setComponent(new Component("string_query")); } } diff --git a/src/main/resources/petriNets/engine-processes/org_group.xml b/src/main/resources/petriNets/engine-processes/org_group.xml index 7a5ad40d303..c06ab7a40e3 100644 --- a/src/main/resources/petriNets/engine-processes/org_group.xml +++ b/src/main/resources/petriNets/engine-processes/org_group.xml @@ -90,7 +90,7 @@ invite_by_mail Add e-mail address example@example.com - Add e-meail address to send invitation + Add e-mail address to send invitation email diff --git a/src/test/groovy/com/netgrif/application/engine/menu/MenuImportExportTest.groovy b/src/test/groovy/com/netgrif/application/engine/menu/MenuImportExportTest.groovy deleted file mode 100644 index 98d06dc2b94..00000000000 --- a/src/test/groovy/com/netgrif/application/engine/menu/MenuImportExportTest.groovy +++ /dev/null @@ -1,202 +0,0 @@ -package com.netgrif.application.engine.menu - -import com.netgrif.application.engine.TestHelper -import com.netgrif.application.engine.auth.domain.Authority -import com.netgrif.application.engine.auth.domain.User -import com.netgrif.application.engine.auth.domain.UserState -import com.netgrif.application.engine.auth.service.UserService -import com.netgrif.application.engine.orgstructure.groups.NextGroupService -import com.netgrif.application.engine.petrinet.domain.I18nString -import com.netgrif.application.engine.petrinet.domain.dataset.FileFieldValue -import com.netgrif.application.engine.petrinet.domain.roles.ProcessRole -import com.netgrif.application.engine.startup.* -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.netgrif.application.engine.workflow.domain.eventoutcomes.dataoutcomes.SetDataEventOutcome -import com.netgrif.application.engine.menu.domain.MenuAndFilters -import com.netgrif.application.engine.workflow.domain.repositories.CaseRepository -import com.netgrif.application.engine.workflow.service.interfaces.IDataService -import com.netgrif.application.engine.workflow.service.interfaces.ITaskService -import com.netgrif.application.engine.workflow.service.interfaces.IWorkflowService -import org.junit.jupiter.api.BeforeEach -import org.junit.jupiter.api.Disabled -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.security.authentication.UsernamePasswordAuthenticationToken -import org.springframework.security.core.Authentication -import org.springframework.security.core.context.SecurityContextHolder -import org.springframework.test.context.ActiveProfiles -import org.springframework.test.context.junit.jupiter.SpringExtension - - -@ExtendWith(SpringExtension.class) -@ActiveProfiles(["test"]) -@SpringBootTest -class MenuImportExportTest { - - public static final String DUMMY_USER_MAIL = "dummy@netgrif.com" - public static final String DUMMY_USER_PASSWORD = "password" - public static final String DUMMY_USER_GROUP_TITLE = "Dummy User" - - private static final String TEST_NET = "mortgage_net.xml" - private static final String TEST_XML_FILE_PATH = "src/test/resources/menu_file_test.xml" - - private static final String GROUP_NAV_TASK = "navigationMenuConfig" - private static final String IMPORT_FILE_FIELD = "import_menu_file" - private static final String EXPORT_FILE_FIELD = "export_menu_file" - - private static final String IMPORT_BUTTON_FIELD = "import_menu_btn" - private static final String EXPORT_BUTTON_FIELD = "export_menu_btn" - private static final String IMPORT_RESULTS_FIELD = "import_results" - private static final String EXPORT_MENUS_FIELD = "menus_for_export" - private static final String IMPORTED_IDS_FIELD = "imported_menu_ids" - private static final String MENU_NAME_FIELD = "menu_identifier" - - private static final String EXPECTED_RESULTS = "\n" + - "IMPORTING MENU \"defaultMenu\":\n" + - "\n" + - "Menu entry \"My cases\": OK\n" + - "\n" + - "Menu entry \"All cases\": OK\n" + - "\n" + - "IMPORTING MENU \"defaultMenu\":\n" + - "\n" + - "Menu entry \"All tasks\": OK\n" + - "\n" + - "Menu entry \"My tasks\": OK\n" - - @Autowired - MenuRunner menuRunner - - @Autowired - TestHelper testHelper - - @Autowired - private CaseRepository repository; - - @Autowired - IWorkflowService workflowService - - @Autowired - ImportHelper importHelper - - @Autowired - ITaskService taskService - - @Autowired - private IDataService dataService - - @Autowired - private GroupRunner groupRunner - - @Autowired - private UserService userService - - @Autowired - private CaseRepository caseRepository - - @Autowired - private NextGroupService nextGroupService - - @Autowired - private SuperCreator superCreator - - private User dummyUser; - - private Authentication userAuth - - @BeforeEach - void beforeTest() { - this.testHelper.truncateDbs(); - this.dummyUser = createDummyUser(); - } - - - @Test - @Disabled("Fix IllegalArgument") - void testMenuImportExport() { - userAuth = new UsernamePasswordAuthenticationToken(dummyUser.transformToLoggedUser(), DUMMY_USER_PASSWORD) - SecurityContextHolder.getContext().setAuthentication(userAuth) - - def testNet = importHelper.createNet(TEST_NET) - assert testNet.isPresent() - - Optional caseOptional = caseRepository.findOne(QCase.case$.title.eq(DUMMY_USER_GROUP_TITLE)); - assert caseOptional.isPresent() - Case groupCase = caseOptional.get() - - File testXmlMenu = new File(TEST_XML_FILE_PATH); - - groupCase.dataSet[IMPORT_FILE_FIELD].value = FileFieldValue.fromString(testXmlMenu.getName() + ":" + testXmlMenu.getPath()) - workflowService.save(groupCase) - - QTask qTask = new QTask("task"); - Task task = taskService.searchOne(qTask.transitionId.eq(GROUP_NAV_TASK).and(qTask.caseId.eq(groupCase.stringId))); - dataService.setData(task, ImportHelper.populateDataset([ - (IMPORT_BUTTON_FIELD): [ - "value": "1", - "type" : "button" - ] - ])) - Optional caseOpt = caseRepository.findOne(QCase.case$.title.eq(DUMMY_USER_GROUP_TITLE)) - assert caseOpt.isPresent() - groupCase = caseOpt.get() - - String importResults = groupCase.getDataField(IMPORT_RESULTS_FIELD).getValue().toString() - assert importResults <=> EXPECTED_RESULTS - - ArrayList imported_ids_list = groupCase.getDataSet().get(IMPORTED_IDS_FIELD).getValue() as ArrayList - assert imported_ids_list.size() == 4 - - Map menusForExportOptions = new LinkedHashMap<>() - String[] split1 = imported_ids_list.get(0).split(",") - String[] split2 = imported_ids_list.get(1).split(",") - String[] split3 = imported_ids_list.get(2).split(",") - String[] split4 = imported_ids_list.get(3).split(",") - - String menuName1 = workflowService.findOne(split1[0]).getDataSet().get(MENU_NAME_FIELD).getValue().toString() - String menuName2 = workflowService.findOne(split3[0]).getDataSet().get(MENU_NAME_FIELD).getValue().toString() - assert menuName1 == "defaultMenu" - assert menuName2 == "newMenu" - - menusForExportOptions.put(split1[0] + "," + split2[0], new I18nString(menuName1)) - menusForExportOptions.put(split3[0] + "," + split4[0], new I18nString(menuName2)) - - groupCase.dataSet[EXPORT_MENUS_FIELD].setOptions(menusForExportOptions) - workflowService.save(groupCase) - - task = taskService.searchOne(qTask.transitionId.eq(GROUP_NAV_TASK).and(qTask.caseId.eq(groupCase.stringId))); - setData(task, [(EXPORT_BUTTON_FIELD): ["type": "button", "value": "1"]]) - - caseOpt = caseRepository.findOne(QCase.case$.title.eq(DUMMY_USER_GROUP_TITLE)) - assert caseOpt.isPresent() - groupCase = caseOpt.get() - - FileFieldValue exportFileField = groupCase.getDataField(EXPORT_FILE_FIELD).getValue() as FileFieldValue - File exportedFiltersFile = new File(exportFileField.getPath()) - assert exportedFiltersFile.exists() - - MenuAndFilters original = menuImportExportService.invokeMethod("loadFromXML", [FileFieldValue.fromString(testXmlMenu.getName() + ":" + testXmlMenu.getPath())] as Object[]) as MenuAndFilters - MenuAndFilters exported = menuImportExportService.invokeMethod("loadFromXML", [exportFileField] as Object[]) as MenuAndFilters - - assert Objects.equals(original, exported); - } - - private User createDummyUser() { - def auths = importHelper.createAuthorities(["user": Authority.user, "admin": Authority.admin]) - return importHelper.createUser(new User(name: "Dummy", surname: "User", email: DUMMY_USER_MAIL, password: DUMMY_USER_PASSWORD, state: UserState.ACTIVE), - [auths.get("user")] as Authority[], - [] as ProcessRole[]) - } - - - private SetDataEventOutcome setData(task, Map> values) { - return dataService.setData(task, ImportHelper.populateDataset(values)) - } - - -} \ No newline at end of file diff --git a/src/test/groovy/com/netgrif/application/engine/validation/CaseFilterFieldValidationTest.groovy b/src/test/groovy/com/netgrif/application/engine/validation/CaseFilterFieldValidationTest.groovy new file mode 100644 index 00000000000..52a168063fa --- /dev/null +++ b/src/test/groovy/com/netgrif/application/engine/validation/CaseFilterFieldValidationTest.groovy @@ -0,0 +1,72 @@ +package com.netgrif.application.engine.validation + +import com.netgrif.application.engine.TestHelper +import com.netgrif.application.engine.petrinet.domain.I18nString +import com.netgrif.application.engine.validation.domain.ValidationDataInput +import com.netgrif.application.engine.validation.models.CaseFilterFieldValidation +import com.netgrif.application.engine.workflow.domain.DataField +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.context.i18n.LocaleContextHolder +import org.springframework.test.context.ActiveProfiles +import org.springframework.test.context.junit.jupiter.SpringExtension + +import java.util.stream.Collectors + +import static org.junit.jupiter.api.Assertions.assertThrows + +@SpringBootTest +@ActiveProfiles(["test"]) +@ExtendWith(SpringExtension.class) +class CaseFilterFieldValidationTest { + + public static final String ErrorMessage = "Invalid Field value" + @Autowired + private TestHelper testHelper + + @BeforeEach + void setup() { + testHelper.truncateDbs() + } + + @Test + void pfqlSuccessTest() { + CaseFilterFieldValidation validation = new CaseFilterFieldValidation() + DataField dataField = new DataField() + dataField.setValue("cases: title eq 'myTitle'") + I18nString validMessage = new I18nString(ErrorMessage) + List rules = [] + ValidationDataInput input = new ValidationDataInput(dataField, validMessage, LocaleContextHolder.getLocale(), rules.stream().skip(1).collect(Collectors.joining(" "))) + + validation.query(input) + } + + @Test + void pfqlWrongResourceTypeTest() { + CaseFilterFieldValidation validation = new CaseFilterFieldValidation() + DataField dataField = new DataField() + dataField.setValue("tasks: caseId eq 'someCaseId'") + I18nString validMessage = new I18nString(ErrorMessage) + List rules = [] + ValidationDataInput input = new ValidationDataInput(dataField, validMessage, LocaleContextHolder.getLocale(), rules.stream().skip(1).collect(Collectors.joining(" "))) + + assertThrows(IllegalArgumentException.class, () -> validation.query(input)) + } + + + @Test + void pfqlWrongQueryTest() { + CaseFilterFieldValidation validation = new CaseFilterFieldValidation() + DataField dataField = new DataField() + dataField.setValue("cases: titleeeee eq 'myTitle'") + I18nString validMessage = new I18nString(ErrorMessage) + List rules = [] + ValidationDataInput input = new ValidationDataInput(dataField, validMessage, LocaleContextHolder.getLocale(), rules.stream().skip(1).collect(Collectors.joining(" "))) + + assertThrows(IllegalArgumentException.class, () -> validation.query(input)) + } + +} diff --git a/src/test/groovy/com/netgrif/application/engine/validation/ProcessFilterFieldValidationTest.groovy b/src/test/groovy/com/netgrif/application/engine/validation/ProcessFilterFieldValidationTest.groovy new file mode 100644 index 00000000000..e53b40fc5be --- /dev/null +++ b/src/test/groovy/com/netgrif/application/engine/validation/ProcessFilterFieldValidationTest.groovy @@ -0,0 +1,72 @@ +package com.netgrif.application.engine.validation + +import com.netgrif.application.engine.TestHelper +import com.netgrif.application.engine.petrinet.domain.I18nString +import com.netgrif.application.engine.validation.domain.ValidationDataInput +import com.netgrif.application.engine.validation.models.ProcessFilterValidation +import com.netgrif.application.engine.workflow.domain.DataField +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.context.i18n.LocaleContextHolder +import org.springframework.test.context.ActiveProfiles +import org.springframework.test.context.junit.jupiter.SpringExtension + +import java.util.stream.Collectors + +import static org.junit.jupiter.api.Assertions.assertThrows + +@SpringBootTest +@ActiveProfiles(["test"]) +@ExtendWith(SpringExtension.class) +class ProcessFilterFieldValidationTest { + + public static final String ErrorMessage = "Invalid Field value" + @Autowired + private TestHelper testHelper + + @BeforeEach + void setup() { + testHelper.truncateDbs() + } + + @Test + void pfqlSuccessTest() { + ProcessFilterValidation validation = new ProcessFilterValidation() + DataField dataField = new DataField() + dataField.setValue("process: identifier eq 'myIdentifier'") + I18nString validMessage = new I18nString(ErrorMessage) + List rules = [] + ValidationDataInput input = new ValidationDataInput(dataField, validMessage, LocaleContextHolder.getLocale(), rules.stream().skip(1).collect(Collectors.joining(" "))) + + validation.query(input) + } + + @Test + void pfqlWrongResourceTypeTest() { + ProcessFilterValidation validation = new ProcessFilterValidation() + DataField dataField = new DataField() + dataField.setValue("cases: title eq 'myTitle'") + I18nString validMessage = new I18nString(ErrorMessage) + List rules = [] + ValidationDataInput input = new ValidationDataInput(dataField, validMessage, LocaleContextHolder.getLocale(), rules.stream().skip(1).collect(Collectors.joining(" "))) + + assertThrows(IllegalArgumentException.class, () -> validation.query(input)) + } + + + @Test + void pfqlWrongQueryTest() { + ProcessFilterValidation validation = new ProcessFilterValidation() + DataField dataField = new DataField() + dataField.setValue("process: identifierrrrrrrr eq 'myIdentifier'") + I18nString validMessage = new I18nString(ErrorMessage) + List rules = [] + ValidationDataInput input = new ValidationDataInput(dataField, validMessage, LocaleContextHolder.getLocale(), rules.stream().skip(1).collect(Collectors.joining(" "))) + + assertThrows(IllegalArgumentException.class, () -> validation.query(input)) + } + +} diff --git a/src/test/groovy/com/netgrif/application/engine/validation/TaskFilterFieldValidationTest.groovy b/src/test/groovy/com/netgrif/application/engine/validation/TaskFilterFieldValidationTest.groovy new file mode 100644 index 00000000000..30eb6c01c6f --- /dev/null +++ b/src/test/groovy/com/netgrif/application/engine/validation/TaskFilterFieldValidationTest.groovy @@ -0,0 +1,72 @@ +package com.netgrif.application.engine.validation + +import com.netgrif.application.engine.TestHelper +import com.netgrif.application.engine.petrinet.domain.I18nString +import com.netgrif.application.engine.validation.domain.ValidationDataInput +import com.netgrif.application.engine.validation.models.TaskFilterFieldValidation +import com.netgrif.application.engine.workflow.domain.DataField +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.context.i18n.LocaleContextHolder +import org.springframework.test.context.ActiveProfiles +import org.springframework.test.context.junit.jupiter.SpringExtension + +import java.util.stream.Collectors + +import static org.junit.jupiter.api.Assertions.assertThrows + +@SpringBootTest +@ActiveProfiles(["test"]) +@ExtendWith(SpringExtension.class) +class TaskFilterFieldValidationTest { + + public static final String ErrorMessage = "Invalid Field value" + @Autowired + private TestHelper testHelper + + @BeforeEach + void setup() { + testHelper.truncateDbs() + } + + @Test + void querySuccessTest() { + TaskFilterFieldValidation validation = new TaskFilterFieldValidation() + DataField dataField = new DataField() + dataField.setValue("tasks: caseId eq 'someCaseId'") + I18nString validMessage = new I18nString(ErrorMessage) + List rules = [] + ValidationDataInput input = new ValidationDataInput(dataField, validMessage, LocaleContextHolder.getLocale(), rules.stream().skip(1).collect(Collectors.joining(" "))) + + validation.query(input) + } + + @Test + void queryWrongResourceTypeTest() { + TaskFilterFieldValidation validation = new TaskFilterFieldValidation() + DataField dataField = new DataField() + dataField.setValue("cases: title eq 'myTitle'") + I18nString validMessage = new I18nString(ErrorMessage) + List rules = [] + ValidationDataInput input = new ValidationDataInput(dataField, validMessage, LocaleContextHolder.getLocale(), rules.stream().skip(1).collect(Collectors.joining(" "))) + + assertThrows(IllegalArgumentException.class, () -> validation.query(input)) + } + + + @Test + void queryWrongQueryTest() { + TaskFilterFieldValidation validation = new TaskFilterFieldValidation() + DataField dataField = new DataField() + dataField.setValue("tasks: caseIddddddd eq 'someCaseId'") + I18nString validMessage = new I18nString(ErrorMessage) + List rules = [] + ValidationDataInput input = new ValidationDataInput(dataField, validMessage, LocaleContextHolder.getLocale(), rules.stream().skip(1).collect(Collectors.joining(" "))) + + assertThrows(IllegalArgumentException.class, () -> validation.query(input)) + } + +} diff --git a/src/test/java/com/netgrif/application/engine/pfql/QueryLangTest.java b/src/test/java/com/netgrif/application/engine/pfql/QueryLangTest.java index 1a96ab8bf13..38ea6de5cbc 100644 --- a/src/test/java/com/netgrif/application/engine/pfql/QueryLangTest.java +++ b/src/test/java/com/netgrif/application/engine/pfql/QueryLangTest.java @@ -98,24 +98,37 @@ public void testSearchService() throws InterruptedException { 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'"); + Object case4 = searchService.search("case: processIdentifier eq 'query_test' and data.text_0.value == '444'"); assertNotNull(case4); assertEquals(Case.class, case4.getClass()); - assertEquals("4", ((Case) case4).getFieldValue("text_0")); + assertEquals("444", ((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")); + Object case6 = searchService.search("case: processIdentifier eq 'query_test' and data.text_1.value neq null"); + assertNotNull(case6); + assertEquals(Case.class, case6.getClass()); + assertNotNull(((Case) case6).getFieldValue("text_1")); + + Object case7 = searchService.search("case: processIdentifier eq 'query_test' and data.text_1.value eq null"); + assertNotNull(case7); + assertEquals(Case.class, case7.getClass()); + assertNull(((Case) case7).getFieldValue("text_1")); + 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'"); + cases = searchService.search("cases: processIdentifier eq 'query_test' and data.boolean_0.value == true and data.text_0.value != '444'"); assertEquals(4, ((Page) cases).getTotalElements()); cases = searchService.search("cases: processIdentifier eq 'query_test' and author eq loggedUser.id"); assertEquals(10, ((Page) cases).getTotalElements()); + + cases = searchService.search("cases: processIdentifier eq 'query_test' and data.text_0.value eq 'x44'*"); + assertEquals(1, ((Page) cases).getTotalElements()); } @Test @@ -139,6 +152,16 @@ public void testSimpleMongodbProcessQuery() { compareMongoQueries(mongoDbUtils, actual, expected); + actual = evaluateQuery("process: id eq null").getFullMongoQuery(); + expected = QPetriNet.petriNet._id.isNull(); + + compareMongoQueries(mongoDbUtils, actual, expected); + + actual = evaluateQuery("process: id neq null").getFullMongoQuery(); + expected = QPetriNet.petriNet._id.isNotNull(); + + compareMongoQueries(mongoDbUtils, actual, expected); + // identifier comparison checkStringComparison(mongoDbUtils, "process", "identifier", QPetriNet.petriNet.identifier); @@ -225,9 +248,24 @@ public void testSimpleMongodbProcessQuery() { compareMongoQueries(mongoDbUtils, actual, expected); + actual = evaluateQuery("process: version not neq null").getFullMongoQuery(); // double negation -> should be null + expected = QPetriNet.petriNet.version.isNull(); + + compareMongoQueries(mongoDbUtils, actual, expected); + + actual = evaluateQuery("process: version not eq null").getFullMongoQuery(); + expected = QPetriNet.petriNet.version.isNotNull(); + + compareMongoQueries(mongoDbUtils, actual, expected); + // title comparison checkStringComparison(mongoDbUtils, "process", "title", QPetriNet.petriNet.title.defaultValue); + actual = evaluateQuery("process: title eq 'somxthing'*").getFullMongoQuery(); + expected = QPetriNet.petriNet.title.defaultValue.likeIgnoreCase("%somxthing%"); + + compareMongoQueries(mongoDbUtils, actual, expected); + // creationDate comparison checkDateComparison(mongoDbUtils, "process", "creationDate", QPetriNet.petriNet.creationDate); } @@ -337,6 +375,16 @@ public void testSimpleMongodbCaseQuery() { compareMongoQueries(mongoDbUtils, actual, expected); + actual = evaluateQuery("case: id eq null").getFullMongoQuery(); + expected = QCase.case$._id.isNull(); + + compareMongoQueries(mongoDbUtils, actual, expected); + + actual = evaluateQuery("case: id neq null").getFullMongoQuery(); + expected = QCase.case$._id.isNotNull(); + + 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); @@ -348,6 +396,16 @@ public void testSimpleMongodbCaseQuery() { compareMongoQueries(mongoDbUtils, actual, expected); + actual = evaluateQuery("case: processId eq null").getFullMongoQuery(); + expected = QCase.case$.petriNetObjectId.isNull(); + + compareMongoQueries(mongoDbUtils, actual, expected); + + actual = evaluateQuery("case: processId neq null").getFullMongoQuery(); + expected = QCase.case$.petriNetObjectId.isNotNull(); + + compareMongoQueries(mongoDbUtils, actual, expected); + // processIdentifier comparison checkStringComparison(mongoDbUtils, "case", "processIdentifier", QCase.case$.processIdentifier); @@ -378,6 +436,16 @@ public void testSimpleMongodbCaseQuery() { compareMongoQueries(mongoDbUtils, actual, expected); + actual = evaluateQuery("case: author eq null").getFullMongoQuery(); + expected = QCase.case$.author.id.isNull(); + + compareMongoQueries(mongoDbUtils, actual, expected); + + actual = evaluateQuery("case: author neq null").getFullMongoQuery(); + expected = QCase.case$.author.id.isNotNull(); + + compareMongoQueries(mongoDbUtils, actual, expected); + actual = evaluateQuery("cases: title in ('test1' : loggedUser.username)").getFullMongoQuery(); expected = QCase.case$.title.gt("test1").and(QCase.case$.title.lt(systemUser.getUsername())); @@ -450,6 +518,12 @@ public void testSimpleMongodbCaseQuery() { actual = evaluateQuery("case: data.field1.value eq true").getFullMongoQuery(); assertNull(actual); + actual = evaluateQuery("case: data.field1.value eq null").getFullMongoQuery(); + assertNull(actual); + + actual = evaluateQuery("case: data.field1.value neq null").getFullMongoQuery(); + assertNull(actual); + // data options comparison actual = evaluateQuery("case: data.field1.options eq 'test'").getFullMongoQuery(); assertNull(actual); @@ -547,6 +621,16 @@ public void testSimpleMongodbTaskQuery() { compareMongoQueries(mongoDbUtils, actual, expected); + actual = evaluateQuery("task: id eq null").getFullMongoQuery(); + expected = QTask.task._id.isNull(); + + compareMongoQueries(mongoDbUtils, actual, expected); + + actual = evaluateQuery("task: id neq null").getFullMongoQuery(); + expected = QTask.task._id.isNotNull(); + + compareMongoQueries(mongoDbUtils, actual, expected); + // transitionId comparison checkStringComparison(mongoDbUtils, "task", "transitionId", QTask.task.transitionId); @@ -581,6 +665,16 @@ public void testSimpleMongodbTaskQuery() { compareMongoQueries(mongoDbUtils, actual, expected); + actual = evaluateQuery("task: userId eq null").getFullMongoQuery(); + expected = QTask.task.userId.isNull(); + + compareMongoQueries(mongoDbUtils, actual, expected); + + actual = evaluateQuery("task: userId neq null").getFullMongoQuery(); + expected = QTask.task.userId.isNotNull(); + + compareMongoQueries(mongoDbUtils, actual, expected); + // caseId comparison actual = evaluateQuery("task: caseId eq 'test'").getFullMongoQuery(); expected = QTask.task.caseId.eq("test"); @@ -597,6 +691,16 @@ public void testSimpleMongodbTaskQuery() { compareMongoQueries(mongoDbUtils, actual, expected); + actual = evaluateQuery("task: caseId eq null").getFullMongoQuery(); + expected = QTask.task.caseId.isNull(); + + compareMongoQueries(mongoDbUtils, actual, expected); + + actual = evaluateQuery("task: caseId neq null").getFullMongoQuery(); + expected = QTask.task.caseId.isNotNull(); + + compareMongoQueries(mongoDbUtils, actual, expected); + // processId comparison actual = evaluateQuery("task: processId eq 'test'").getFullMongoQuery(); expected = QTask.task.processId.eq("test"); @@ -613,6 +717,16 @@ public void testSimpleMongodbTaskQuery() { compareMongoQueries(mongoDbUtils, actual, expected); + actual = evaluateQuery("task: processId eq null").getFullMongoQuery(); + expected = QTask.task.processId.isNull(); + + compareMongoQueries(mongoDbUtils, actual, expected); + + actual = evaluateQuery("task: processId neq null").getFullMongoQuery(); + expected = QTask.task.processId.isNotNull(); + + compareMongoQueries(mongoDbUtils, actual, expected); + // lastAssign comparison // TODO: fix // checkDateComparison(mongoDbUtils, "task", "lastAssign", QTask.task.lastAssigned); @@ -718,6 +832,16 @@ public void testSimpleMongodbUserQuery() { compareMongoQueries(mongoDbUtils, actual, expected); + actual = evaluateQuery("user: id eq null").getFullMongoQuery(); + expected = QUser.user._id.isNull(); + + compareMongoQueries(mongoDbUtils, actual, expected); + + actual = evaluateQuery("user: id neq null").getFullMongoQuery(); + expected = QUser.user._id.isNotNull(); + + compareMongoQueries(mongoDbUtils, actual, expected); + // name comparison checkStringComparison(mongoDbUtils, "user", "name", QUser.user.name); @@ -832,41 +956,32 @@ public void testSimpleElasticProcessQuery() { 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); } @@ -877,7 +992,6 @@ public void testComplexElasticProcessQuery() { // 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); @@ -912,7 +1026,6 @@ public void testComplexElasticProcessQuery() { 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); @@ -945,6 +1058,14 @@ public void testSimpleElasticCaseQuery() { expected = String.format("stringId:(%s OR %s)", GENERIC_OBJECT_ID, GENERIC_OBJECT_ID); assertEquals(expected, actual); + actual = evaluateQuery("case: id eq null").getFullElasticQuery(); + expected = "!(_exists_:stringId)"; + assertEquals(expected, actual); + + actual = evaluateQuery("case: id neq null").getFullElasticQuery(); + expected = "_exists_:stringId"; + 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); @@ -954,6 +1075,14 @@ public void testSimpleElasticCaseQuery() { expected = String.format("processId:(%s OR %s)", GENERIC_OBJECT_ID, GENERIC_OBJECT_ID); assertEquals(expected, actual); + actual = evaluateQuery("case: processId not neq null").getFullElasticQuery(); // double negation -> eq null + expected = "!(_exists_:processId)"; + assertEquals(expected, actual); + + actual = evaluateQuery("case: processId not eq null").getFullElasticQuery(); + expected = "_exists_:processId"; + assertEquals(expected, actual); + // processIdentifier comparison checkStringComparisonElastic("case", "processIdentifier", "processIdentifier"); @@ -985,6 +1114,14 @@ public void testSimpleElasticCaseQuery() { expected = String.format("author:(%s OR %s OR %s)", GENERIC_OBJECT_ID, GENERIC_OBJECT_ID, new ObjectId(systemUser.getId())); assertEquals(expected, actual); + actual = evaluateQuery("case: author eq null").getFullElasticQuery(); + expected = "!(_exists_:author)"; + assertEquals(expected, actual); + + actual = evaluateQuery("case: author neq null").getFullElasticQuery(); + expected = "_exists_:author"; + assertEquals(expected, actual); + // places comparison checkNumberComparisonElastic("case", "places.p1.marking", "places.p1.marking"); @@ -1010,6 +1147,14 @@ public void testSimpleElasticCaseQuery() { expected = String.format("tasks.t1.userId:(%s OR %s)", GENERIC_OBJECT_ID, GENERIC_OBJECT_ID); assertEquals(expected, actual); + actual = evaluateQuery("case: tasks.t1.userId eq null").getFullElasticQuery(); + expected = "!(_exists_:tasks.t1.userId)"; + assertEquals(expected, actual); + + actual = evaluateQuery("case: tasks.t1.userId neq null").getFullElasticQuery(); + expected = "_exists_:tasks.t1.userId"; + assertEquals(expected, actual); + // data value comparison checkStringComparisonElastic("case", "data.field1.value", "dataSet.field1.fulltextValue"); @@ -1017,6 +1162,14 @@ public void testSimpleElasticCaseQuery() { checkDateComparisonElastic("case", "data.field3.value", "dataSet.field3.timestampValue"); + actual = evaluateQuery("case: data.field1.value eq 'somxthing'*").getFullElasticQuery(); + expected = "dataSet.field1.fulltextValue:somxthing~AUTO"; + assertEquals(expected, actual); + + actual = evaluateQuery("case: data.field1.value neq 'somxthing'*").getFullElasticQuery(); + expected = "NOT dataSet.field1.fulltextValue:somxthing~AUTO"; + assertEquals(expected, actual); + actual = evaluateQuery("case: data.field1.value eq true").getFullElasticQuery(); expected = "dataSet.field1.booleanValue:true"; assertEquals(expected, actual); @@ -1029,6 +1182,18 @@ public void testSimpleElasticCaseQuery() { expected = "dataSet.field1.booleanValue:false"; assertEquals(expected, actual); + actual = evaluateQuery("case: data.field1.value eq null").getFullElasticQuery(); + expected = "!(_exists_:dataSet.field1.fulltextValue)"; + assertEquals(expected, actual); + + actual = evaluateQuery("case: data.field1.value neq null").getFullElasticQuery(); + expected = "_exists_:dataSet.field1.fulltextValue"; + assertEquals(expected, actual); + + actual = evaluateQuery("case: data.field1.value eq ''").getFullElasticQuery(); + expected = "dataSet.field1.fulltextValue.keyword:\"\""; + assertEquals(expected, actual); + // data options comparison checkStringComparisonElastic("case", "data.field1.options", "dataSet.field1.options"); @@ -1043,8 +1208,40 @@ public void testSimpleElasticCaseQuery() { assertEquals(expected, actual); actual = evaluateQuery("cases: title in (loggedUser.username : loggedUser.fullName)").getFullElasticQuery(); - expected = String.format("(title:>%s AND title:<%s)", systemUser.getUsername(), systemUser.getFullName()); + expected = String.format("(title:>%s AND title:<%s)", systemUser.getUsername(), "\"" + systemUser.getFullName() + "\""); + + assertEquals(expected, actual); + + actual = evaluateQuery("case: title eq null").getFullElasticQuery(); + expected = "!(_exists_:title)"; + assertEquals(expected, actual); + + actual = evaluateQuery("case: title neq null").getFullElasticQuery(); + expected = "_exists_:title"; + assertEquals(expected, actual); + + actual = evaluateQuery("case: title eq 'white space test 1'").getFullElasticQuery(); + expected = "title.keyword:\"white space test 1\""; + assertEquals(expected, actual); + + actual = evaluateQuery("case: title eq 'white\tspace\ttest\t1'").getFullElasticQuery(); + expected = "title.keyword:\"white\tspace\ttest\t1\""; + assertEquals(expected, actual); + actual = evaluateQuery("case: title eq 'somxthing'*").getFullElasticQuery(); + expected = "title:somxthing~AUTO"; + assertEquals(expected, actual); + + actual = evaluateQuery("case: title not eq 'somxthing anxthing'*").getFullElasticQuery(); + expected = "NOT title:(somxthing~AUTO AND anxthing~AUTO)"; + assertEquals(expected, actual); + + actual = evaluateQuery("case: title not eq 'somxthing anxthing everxthing '*").getFullElasticQuery(); + expected = "NOT title:(somxthing~AUTO AND anxthing~AUTO AND everxthing~AUTO)"; + assertEquals(expected, actual); + + actual = evaluateQuery("case: title eq 'needsTo/BeEscaped'").getFullElasticQuery(); + expected = "title.keyword:needsTo\\/BeEscaped"; assertEquals(expected, actual); } @@ -1061,40 +1258,40 @@ public void testComplexElasticCaseQuery() { // 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); + expected = String.format("stringId:%s AND title.keyword: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); + expected = String.format("stringId:%s AND NOT title.keyword: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); + expected = String.format("stringId:%s AND NOT title.keyword: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); + expected = String.format("stringId:%s OR title.keyword: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); + expected = String.format("stringId:%s OR NOT title.keyword: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); + expected = String.format("stringId:%s OR NOT title.keyword: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); + expected = String.format("stringId:%s AND (title.keyword:test OR title.keyword: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); + expected = String.format("stringId:%s AND (title.keyword:test OR (title.keyword:test1 AND processIdentifier:test))", GENERIC_OBJECT_ID); assertEquals(expected, actual); } @@ -1102,26 +1299,56 @@ public void testComplexElasticCaseQuery() { public void testSimpleElasticTaskQuery() { // without comparison String actual = evaluateQuery("tasks").getFullElasticQuery(); - assertEquals("*", actual); + String expected = "*"; + assertEquals(expected, actual); // elastic query should be always null // id comparison actual = evaluateQuery(String.format("task: id eq '%s'", GENERIC_OBJECT_ID)).getFullElasticQuery(); - assertNull(actual); + expected = String.format("stringId:%s", GENERIC_OBJECT_ID); + assertEquals(expected, actual); + + actual = evaluateQuery("task: id eq null").getFullElasticQuery(); + expected = "!(_exists_:stringId)"; + assertEquals(expected, actual); + + actual = evaluateQuery("task: id neq null").getFullElasticQuery(); + expected = "_exists_:stringId"; + assertEquals(expected, actual); // transitionId comparison actual = evaluateQuery("task: transitionId eq 'test'").getFullElasticQuery(); - assertNull(actual); + expected = "transitionId:test"; + assertEquals(expected, actual); actual = evaluateQuery("task: transitionId contains 'test'").getFullElasticQuery(); - assertNull(actual); + expected = "transitionId:*test*"; + assertEquals(expected, actual); + + actual = evaluateQuery("task: transitionId eq null").getFullElasticQuery(); + expected = "!(_exists_:transitionId)"; + assertEquals(expected, actual); + + actual = evaluateQuery("task: transitionId neq null").getFullElasticQuery(); + expected = "_exists_:transitionId"; + assertEquals(expected, actual); // title comparison actual = evaluateQuery("task: title eq 'test'").getFullElasticQuery(); - assertNull(actual); + expected = "title.keyword:test"; + assertEquals(expected, actual); actual = evaluateQuery("task: title contains 'test'").getFullElasticQuery(); - assertNull(actual); + expected = "title:*test*"; + assertEquals(expected, actual); + + actual = evaluateQuery("task: title eq null").getFullElasticQuery(); + expected = "!(_exists_:title)"; + assertEquals(expected, actual); + + actual = evaluateQuery("task: title neq null").getFullElasticQuery(); + expected = "_exists_:title"; + assertEquals(expected, actual); // state comparison actual = evaluateQuery("task: state eq enabled").getFullElasticQuery(); @@ -1132,24 +1359,54 @@ public void testSimpleElasticTaskQuery() { // userId comparison actual = evaluateQuery("task: userId eq 'test'").getFullElasticQuery(); - assertNull(actual); + expected = "userId:test"; + assertEquals(expected, actual); actual = evaluateQuery("task: userId contains 'test'").getFullElasticQuery(); - assertNull(actual); + expected = "userId:*test*"; + assertEquals(expected, actual); + + actual = evaluateQuery("task: userId eq null").getFullElasticQuery(); + expected = "!(_exists_:userId)"; + assertEquals(expected, actual); + + actual = evaluateQuery("task: userId neq null").getFullElasticQuery(); + expected = "_exists_:userId"; + assertEquals(expected, actual); // caseId comparison actual = evaluateQuery("task: caseId eq 'test'").getFullElasticQuery(); - assertNull(actual); + expected = "caseId:test"; + assertEquals(expected, actual); actual = evaluateQuery("task: caseId contains 'test'").getFullElasticQuery(); - assertNull(actual); + expected = "caseId:*test*"; + assertEquals(expected, actual); + + actual = evaluateQuery("task: caseId eq null").getFullElasticQuery(); + expected = "!(_exists_:caseId)"; + assertEquals(expected, actual); + + actual = evaluateQuery("task: caseId neq null").getFullElasticQuery(); + expected = "_exists_:caseId"; + assertEquals(expected, actual); // processId comparison actual = evaluateQuery("task: processId eq 'test'").getFullElasticQuery(); - assertNull(actual); + expected = "processId:test"; + assertEquals(expected, actual); actual = evaluateQuery("task: processId contains 'test'").getFullElasticQuery(); - assertNull(actual); + expected = "processId:*test*"; + assertEquals(expected, actual); + + actual = evaluateQuery("task: processId eq null").getFullElasticQuery(); + expected = "!(_exists_:processId)"; + assertEquals(expected, actual); + + actual = evaluateQuery("task: processId neq null").getFullElasticQuery(); + expected = "_exists_:processId"; + assertEquals(expected, actual); // lastAssign comparison actual = evaluateQuery("task: lastAssign eq 2011-12-03T10:15:30").getFullElasticQuery(); @@ -1189,44 +1446,55 @@ 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); + String expected = String.format("NOT stringId:%s", GENERIC_OBJECT_ID); + assertEquals(expected, actual); actual = evaluateQuery(String.format("task: id neq '%s'", GENERIC_OBJECT_ID)).getFullElasticQuery(); - assertNull(actual); + expected = String.format("NOT stringId:%s", GENERIC_OBJECT_ID); + assertEquals(expected, actual); // and comparison actual = evaluateQuery(String.format("task: id eq '%s' and title eq 'test'", GENERIC_OBJECT_ID)).getFullElasticQuery(); - assertNull(actual); + expected = String.format("stringId:%s AND title.keyword:test", GENERIC_OBJECT_ID); + assertEquals(expected, actual); // and not comparison actual = evaluateQuery(String.format("task: id eq '%s' and title not eq 'test'", GENERIC_OBJECT_ID)).getFullElasticQuery(); - assertNull(actual); + expected = String.format("stringId:%s AND NOT title.keyword:test", GENERIC_OBJECT_ID); + assertEquals(expected, actual); actual = evaluateQuery(String.format("task: id eq '%s' and title != 'test'", GENERIC_OBJECT_ID)).getFullElasticQuery(); - assertNull(actual); + expected = String.format("stringId:%s AND NOT title.keyword:test", GENERIC_OBJECT_ID); + assertEquals(expected, actual); // or comparison actual = evaluateQuery(String.format("task: id eq '%s' or title eq 'test'", GENERIC_OBJECT_ID)).getFullElasticQuery(); - assertNull(actual); + expected = String.format("stringId:%s OR title.keyword:test", GENERIC_OBJECT_ID); + assertEquals(expected, actual); // or not comparison actual = evaluateQuery(String.format("task: id eq '%s' or title not eq 'test'", GENERIC_OBJECT_ID)).getFullElasticQuery(); - assertNull(actual); + expected = String.format("stringId:%s OR NOT title.keyword:test", GENERIC_OBJECT_ID); + assertEquals(expected, actual); actual = evaluateQuery(String.format("task: id eq '%s' or title neq 'test'", GENERIC_OBJECT_ID)).getFullElasticQuery(); - assertNull(actual); + expected = String.format("stringId:%s OR NOT title.keyword:test", GENERIC_OBJECT_ID); + assertEquals(expected, 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); + expected = String.format("stringId:%s AND (title.keyword:test OR title.keyword:test1)", GENERIC_OBJECT_ID); + assertEquals(expected, 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); + expected = String.format("stringId:%s AND NOT (title.keyword:test OR title.keyword:test1)", GENERIC_OBJECT_ID); + assertEquals(expected, 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); + expected = String.format("stringId:%s AND (title.keyword:test OR (title.keyword:test1 AND processId:test))", GENERIC_OBJECT_ID); + assertEquals(expected, actual); } @Test @@ -1800,6 +2068,8 @@ public void testProcessQueriesFail() { assertThrows(IllegalArgumentException.class, () -> evaluateQuery("process email eq 'test'")); assertThrows(IllegalArgumentException.class, () -> evaluateQuery("process page 2")); assertThrows(IllegalArgumentException.class, () -> evaluateQuery("process:")); + assertThrows(IllegalArgumentException.class, () -> evaluateQuery("process: identifier gt null")); + assertThrows(IllegalArgumentException.class, () -> evaluateQuery("process: identifier contains null")); } @Test @@ -1821,6 +2091,8 @@ public void testCaseQueriesFail() { assertThrows(IllegalArgumentException.class, () -> evaluateQuery("case:")); assertThrows(IllegalArgumentException.class, () -> evaluateQuery("case: creationDate eq loggedUser.id")); assertThrows(IllegalArgumentException.class, () -> evaluateQuery("case: processIdentifier eq loggedUser.anonymous")); + assertThrows(IllegalArgumentException.class, () -> evaluateQuery("case: processIdentifier lt null")); + assertThrows(IllegalArgumentException.class, () -> evaluateQuery("case: title contains null")); } @Test @@ -1841,6 +2113,8 @@ public void testTaskQueriesFail() { assertThrows(IllegalArgumentException.class, () -> evaluateQuery("task email eq 'test'")); assertThrows(IllegalArgumentException.class, () -> evaluateQuery("task page 2")); assertThrows(IllegalArgumentException.class, () -> evaluateQuery("task:")); + assertThrows(IllegalArgumentException.class, () -> evaluateQuery("task: id gte null")); + assertThrows(IllegalArgumentException.class, () -> evaluateQuery("task: userId in (null, 'test')")); } @Test @@ -1946,6 +2220,25 @@ private static void checkStringComparison(MongoDbUtils mongoDbUtils, String r expected = stringPath.gt("test1").and(stringPath.loe("test2")).not(); compareMongoQueries(mongoDbUtils, actual, expected); + + actual = evaluateQuery(String.format("%s: %s eq null", resource, attribute)).getFullMongoQuery(); + expected = stringPath.isNull(); + + if (stringPath.toString().contains(".defaultValue")) { + assertEquals(actual.toString(), expected.toString().replaceAll(".defaultValue", "")); + } else { + compareMongoQueries(mongoDbUtils, actual, expected); + } + + actual = evaluateQuery(String.format("%s: %s neq null", resource, attribute)).getFullMongoQuery(); + expected = stringPath.isNotNull(); + + if (stringPath.toString().contains(".defaultValue")) { + assertEquals(actual.toString(), expected.toString().replaceAll(".defaultValue", "")); + } else { + compareMongoQueries(mongoDbUtils, actual, expected); + } + } private static void checkDateComparison(MongoDbUtils mongoDbUtils, String resource, String attribute, DateTimePath dateTimePath) { @@ -2030,21 +2323,27 @@ private static void checkDateComparison(MongoDbUtils mongoDbUtils, String res expected = dateTimePath.gt(date4).and(dateTimePath.loe(date5)).not(); compareMongoQueries(mongoDbUtils, actual, expected); + + actual = evaluateQuery(String.format("%s: %s eq null", resource, attribute)).getFullMongoQuery(); + expected = dateTimePath.isNull(); + + compareMongoQueries(mongoDbUtils, actual, expected); + + actual = evaluateQuery(String.format("%s: %s neq null", resource, attribute)).getFullMongoQuery(); + expected = dateTimePath.isNotNull(); + + 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; - if (resultAttribute.matches("dataSet\\.[^.]*\\.fulltextValue")) { - expected = String.format("%s.keyword:test", resultAttribute); - } else { - expected = String.format("%s:test", resultAttribute); - } + String expectedWithKeyword = String.format("%s.keyword:test", resultAttribute); + String expectedWithoutKeyword = String.format("%s:test", resultAttribute); - assertEquals(expected, actual); + assertTrue(actual.equals(expectedWithKeyword) || actual.equals(expectedWithoutKeyword)); actual = evaluateQuery(String.format("%s: %s contains 'test'", resource, attribute)).getFullElasticQuery(); - expected = String.format("%s:*test*", resultAttribute); + String expected = String.format("%s:*test*", resultAttribute); assertEquals(expected, actual); @@ -2092,6 +2391,16 @@ private static void checkStringComparisonElastic(String resource, String attribu expected = String.format("NOT (%s:>test1 AND %s:<=test2)", resultAttribute, resultAttribute); assertEquals(expected, actual); + + actual = evaluateQuery(String.format("%s: %s eq null", resource, attribute)).getFullElasticQuery(); + expected = String.format("!(_exists_:%s)", resultAttribute); + + assertEquals(expected, actual); + + actual = evaluateQuery(String.format("%s: %s neq null", resource, attribute)).getFullElasticQuery(); + expected = String.format("_exists_:%s", resultAttribute); + + assertEquals(expected, actual); } private static void checkNumberComparisonElastic(String resource, String attribute, String resultAttribute) { @@ -2144,6 +2453,17 @@ private static void checkNumberComparisonElastic(String resource, String attribu expected = String.format("NOT (%s:>1 AND %s:<=2)", resultAttribute, resultAttribute); assertEquals(expected, actual); + + String resultAttributeWithFulltextValue = resultAttribute.replaceAll(".numberValue", ".fulltextValue"); + actual = evaluateQuery(String.format("%s: %s eq null", resource, attribute)).getFullElasticQuery(); + expected = String.format("!(_exists_:%s)", resultAttributeWithFulltextValue); + + assertEquals(expected, actual); + + actual = evaluateQuery(String.format("%s: %s neq null", resource, attribute)).getFullElasticQuery(); + expected = String.format("_exists_:%s", resultAttributeWithFulltextValue); + + assertEquals(expected, actual); } private static void checkDateComparisonElastic(String resource, String attribute, String resultAttribute) { @@ -2228,6 +2548,17 @@ private static void checkDateComparisonElastic(String resource, String attribute expected = String.format("NOT (%s:>%s AND %s:<=%s)", resultAttribute, Timestamp.valueOf(date4).getTime(), resultAttribute, Timestamp.valueOf(date5).getTime()); assertEquals(expected, actual); + + String resultAttributeWithFulltextValue = resultAttribute.replaceAll(".timestampValue", ".fulltextValue"); + actual = evaluateQuery(String.format("%s: %s eq null", resource, attribute)).getFullElasticQuery(); + expected = String.format("!(_exists_:%s)", resultAttributeWithFulltextValue); + + assertEquals(expected, actual); + + actual = evaluateQuery(String.format("%s: %s not eq null", resource, attribute)).getFullElasticQuery(); + expected = String.format("_exists_:%s", resultAttributeWithFulltextValue); + + assertEquals(expected, actual); } private static void compareMongoQueries(MongoDbUtils mongoDbUtils, Predicate actual, Predicate expected) { diff --git a/src/test/resources/menu_file_test.xml b/src/test/resources/menu_file_test.xml deleted file mode 100644 index eed7edc13aa..00000000000 --- a/src/test/resources/menu_file_test.xml +++ /dev/null @@ -1,191 +0,0 @@ - - - - - - My cases - 618b9afb5f420c67b6d6ce66 - - client - mortgage - - - account_clerk - mortgage - - - loan_officer - mortgage - - - property_appraiser - mortgage - - - - All cases - 618b9afc5f420c67b6d6d36b - - client - mortgage - - - account_clerk - mortgage - - - loan_officer - mortgage - - - property_appraiser - mortgage - - - - - - All tasks - 618b9af95f420c67b6d6c45c - - client - mortgage - - - account_clerk - mortgage - - - loan_officer - mortgage - - - property_appraiser - mortgage - - - - My tasks - 618b9afa5f420c67b6d6c961 - - - - - - 618b9afb5f420c67b6d6ce66 - - My cases - - Meine Fälle - Moje prípady - - - (author:<<me>>) - public - Case - assignment_ind - - Case - true - true - - case_author - - - - - case_author - - equals - - - - search.category.userMe - - <<me>> - - - - - - - - - - 618b9afc5f420c67b6d6d36b - - All cases - - Alle Fälle - Všetky prípady - - - public - Case - assignment - - Case - true - true - - - - 618b9af95f420c67b6d6c45c - - All tasks - - Alle Aufgaben - Všetky úlohy - - - public - Task - library_add_check - - Task - true - true - - - - 618b9afa5f420c67b6d6c961 - - My tasks - - Meine Aufgaben - Moje úlohy - - - (userId:<<me>>) - public - Task - account_box - - Task - true - true - - task_assignee - - - - - task_assignee - - equals - - - - search.category.userMe - - <<me>> - - - - - - - - - - diff --git a/src/test/resources/petriNets/pfql.xml b/src/test/resources/petriNets/pfql.xml index 1dd151d8199..d477094aef2 100644 --- a/src/test/resources/petriNets/pfql.xml +++ b/src/test/resources/petriNets/pfql.xml @@ -25,7 +25,12 @@ text_0 Text - params["id"] + params["id"] + params["id"] + params["id"] + + + text_1 + Text + params["id"] == "0" ? null : params["id"] t1