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