-
Notifications
You must be signed in to change notification settings - Fork 6
[NAE-1770] Validation register frontend #293
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
base: release/8.0.0
Are you sure you want to change the base?
Changes from all commits
57b83d6
a34f7bf
5f7f0e0
4834207
3c60c21
b91b842
d0679f6
ce181ff
7458e09
aa11d67
e536b09
47f762a
b08edcf
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,145 @@ | ||
package com.netgrif.application.engine.petrinet.domain.dataset.logic.action | ||
|
||
|
||
import com.netgrif.application.engine.petrinet.domain.dataset.* | ||
import groovy.util.logging.Slf4j | ||
|
||
import java.time.LocalDate | ||
import java.time.LocalDateTime | ||
import java.time.format.DateTimeFormatter | ||
import java.time.format.DateTimeParseException | ||
|
||
@Slf4j | ||
class ValidationDelegate { | ||
|
||
public static final String FUTURE = 'future' | ||
public static final String TODAY = 'today' | ||
public static final String PAST = 'past' | ||
public static final String NOW = 'now' | ||
public static final String INF = 'inf' | ||
public static final String TEL_NUMBER_REGEX = '^(?:\\+?(\\d{1,3}))?([-. (]*(\\d{3})[-. )]*)?((\\d{3})[-. ]*(\\d{2,4})(?:[-.x ]*(\\d+))?)$' | ||
public static final String EMAIL_REGEX = '^[a-zA-Z0-9\\._\\%\\+\\-]+@[a-zA-Z0-9\\.\\-]+\\.[a-zA-Z]{2,}$' | ||
|
||
// todo NAE-1788: thisField keyword | ||
Field<?> thisField | ||
|
||
Boolean notEmpty() { return thisField.rawValue != null } | ||
|
||
// boolean field validations | ||
Boolean requiredTrue() { return thisField instanceof BooleanField && notEmpty() && thisField.rawValue == true } | ||
|
||
// date field validations | ||
Boolean between(def from, def to) { | ||
if (!(thisField instanceof DateField || thisField instanceof DateTimeField)) { | ||
return false | ||
} | ||
|
||
LocalDateTime updateDate_TODAY = thisField instanceof DateField ? LocalDate.now().atStartOfDay() : LocalDateTime.now() | ||
LocalDateTime thisFieldValue = thisField.rawValue instanceof LocalDateTime ? thisField.rawValue : thisField.rawValue.atStartOfDay() | ||
|
||
def fromDate = from | ||
if (from instanceof String) { | ||
LocalDate parsedDate = parseStringToLocalDate(from) | ||
fromDate = parsedDate ? parsedDate.atStartOfDay() : from | ||
} | ||
|
||
def toDate = to | ||
if (to instanceof String) { | ||
LocalDate parsedDate = parseStringToLocalDate(to) | ||
toDate = parsedDate ? parsedDate.atStartOfDay() : to | ||
} | ||
|
||
if ((fromDate == TODAY || fromDate == NOW) && toDate == FUTURE) { | ||
if (thisFieldValue < updateDate_TODAY) { | ||
return false | ||
} | ||
} else if (fromDate == PAST && (toDate == TODAY || toDate == NOW)) { | ||
if (thisFieldValue > updateDate_TODAY) { | ||
return false | ||
} | ||
} else if (fromDate == PAST && (toDate instanceof LocalDateTime)) { | ||
if (thisFieldValue > toDate) { | ||
return false | ||
} | ||
} else if (fromDate == TODAY && (toDate instanceof LocalDateTime)) { | ||
if (thisFieldValue > toDate || thisFieldValue < updateDate_TODAY) { | ||
return false | ||
} | ||
} else if ((fromDate instanceof LocalDateTime) && toDate == TODAY) { | ||
if (thisFieldValue < fromDate || thisFieldValue > updateDate_TODAY) { | ||
return false | ||
} | ||
} else if (toDate == FUTURE && (fromDate instanceof LocalDateTime)) { | ||
if (thisFieldValue < fromDate) { | ||
return false | ||
} | ||
} else if ((fromDate instanceof LocalDateTime) && (toDate instanceof LocalDateTime)) { | ||
if (thisFieldValue > toDate || thisFieldValue < fromDate) { | ||
return false | ||
} | ||
} | ||
return true | ||
} | ||
|
||
Boolean workday() { return (thisField instanceof DateField || thisField instanceof DateTimeField) && notEmpty() && !thisField.rawValue.dayOfWeek.isWeekend() } | ||
|
||
|
||
Boolean weekend() { return (thisField instanceof DateField || thisField instanceof DateTimeField) && notEmpty() && thisField.rawValue.dayOfWeek.isWeekend() } | ||
|
||
protected static LocalDate parseStringToLocalDate(String stringDate) { | ||
if (stringDate == null) { | ||
return null | ||
} | ||
List<String> patterns = Arrays.asList("dd.MM.yyyy", "") | ||
try { | ||
return LocalDate.parse(stringDate, DateTimeFormatter.BASIC_ISO_DATE) | ||
} catch (DateTimeParseException ignored) { | ||
try { | ||
return LocalDate.parse(stringDate, DateTimeFormatter.ISO_DATE) | ||
} catch (DateTimeParseException ignored2) { | ||
for (String pattern : patterns) { | ||
try { | ||
return LocalDateTime.parse(stringDate, DateTimeFormatter.ofPattern(pattern)) | ||
} catch (DateTimeParseException | IllegalArgumentException ignored3) { | ||
} | ||
} | ||
} | ||
} | ||
return null | ||
} | ||
|
||
// number field validations | ||
Boolean odd() { return thisField instanceof NumberField && notEmpty() && thisField.rawValue as Double % 2 != 0 } | ||
|
||
Boolean even() { return thisField instanceof NumberField && notEmpty() && thisField.rawValue as Double % 2 == 0 } | ||
|
||
Boolean positive() { return thisField instanceof NumberField && notEmpty() && thisField.rawValue >= 0 } | ||
|
||
Boolean negative() { return thisField instanceof NumberField && notEmpty() && thisField.rawValue <= 0 } | ||
|
||
Boolean decimal() { return thisField instanceof NumberField && notEmpty() && thisField.rawValue as Double % 1 == 0 } | ||
|
||
Boolean inrange(def from, def to) { | ||
|
||
if (from instanceof String && from.toLowerCase() == INF) { | ||
from = Double.MIN_VALUE | ||
} | ||
|
||
if (to instanceof String && to.toLowerCase() == INF) { | ||
to = Double.MAX_VALUE | ||
} | ||
return thisField instanceof NumberField && notEmpty() && thisField.rawValue >= from as Double && thisField.rawValue <= to as Double | ||
} | ||
|
||
// text field validations | ||
Boolean regex(String pattern) { return thisField instanceof TextField && notEmpty() && thisField.rawValue ==~ pattern } | ||
|
||
Boolean minLength(Integer minLength) { return thisField instanceof TextField && notEmpty() && (thisField.rawValue as String).length() >= minLength } | ||
|
||
Boolean maxLength(Integer maxLength) { return thisField instanceof TextField && notEmpty() && (thisField.rawValue as String).length() <= maxLength } | ||
|
||
Boolean telNumber() { return regex(TEL_NUMBER_REGEX) } | ||
|
||
Boolean email() { return regex(EMAIL_REGEX) } | ||
|
||
} |
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,94 @@ | ||
package com.netgrif.application.engine.petrinet.domain.dataset.logic.action | ||
|
||
import com.netgrif.application.engine.event.IGroovyShellFactory | ||
import com.netgrif.application.engine.petrinet.domain.dataset.Field | ||
import com.netgrif.application.engine.petrinet.domain.dataset.Validation | ||
import com.netgrif.application.engine.validations.ValidationRegistry | ||
import com.netgrif.application.engine.workflow.domain.Case | ||
import groovy.util.logging.Slf4j | ||
import org.springframework.beans.factory.annotation.Autowired | ||
import org.springframework.beans.factory.annotation.Lookup | ||
import org.springframework.stereotype.Component | ||
|
||
@Slf4j | ||
@Component | ||
abstract class ValidationExecutioner { | ||
|
||
@Lookup("validationDelegate") | ||
abstract ValidationDelegate getValidationDelegate() | ||
|
||
@Autowired | ||
private ValidationRegistry registry; | ||
|
||
@Autowired | ||
private IGroovyShellFactory shellFactory | ||
|
||
void execute(Case useCase, Field<?> field, List<Validation> validations) { | ||
if (!validations) { | ||
return | ||
} | ||
|
||
log.info("Validations: ${validations.collect { it.name }}") | ||
|
||
ValidationDelegate delegate = initDelegate(useCase, field, validations.collect { it.name }) | ||
validations.each { validation -> | ||
runValidation(field, validation, delegate) | ||
} | ||
} | ||
|
||
protected void runValidation(Field<?> field, Validation validation, ValidationDelegate delegate) { | ||
if (field.rawValue == null) { | ||
return | ||
} | ||
|
||
Closure<Boolean> code = initCode(validation, delegate) | ||
if (!code()) { | ||
throw new IllegalArgumentException(validation.message.toString()) | ||
} | ||
} | ||
|
||
protected Closure<Boolean> getValidationCode(String validationName) { | ||
return this.registry.getValidation(validationName) | ||
} | ||
|
||
protected static String escapeSpecialCharacters(String s){ | ||
return s.replace('\\', '\\\\') | ||
.replace('\'', '\\\'') | ||
} | ||
|
||
protected Closure<Boolean> initCode(Validation validation, ValidationDelegate delegate) { | ||
List<String> argumentList = [] | ||
if (validation.serverArguments != null) { | ||
argumentList = validation.serverArguments.argument.collect { it.isDynamic ? it.value : "'${escapeSpecialCharacters(it.value)}'" } | ||
} | ||
String validationCall = "${validation.name}(${argumentList.join(", ")})" | ||
Closure<Boolean> code = this.shellFactory.getGroovyShell().evaluate("{ -> return " + validationCall + " }") as Closure<Boolean> | ||
return code.rehydrate(delegate, code.owner, code.thisObject) | ||
} | ||
|
||
protected ValidationDelegate initDelegate(Case useCase, Field<?> thisField, List<String> validationNames) { | ||
ValidationDelegate delegate = getValidationDelegate() | ||
delegate.metaClass.useCase = useCase | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Why isn't |
||
useCase.dataSet.fields.values().forEach { Field<?> field -> | ||
delegate.metaClass."$field.importId" = field | ||
} | ||
|
||
validationNames = filterConflictedValidationNames(useCase, validationNames) | ||
validationNames.each { validationName -> | ||
delegate.metaClass."$validationName" = getValidationCode(validationName) | ||
} | ||
|
||
delegate.thisField = thisField | ||
return delegate | ||
} | ||
|
||
private static List<String> filterConflictedValidationNames(Case useCase, List<String> validationNames) { | ||
List<String> fieldNames = useCase.dataSet.fields.keySet() as List<String> | ||
fieldNames.retainAll(validationNames) | ||
if (!fieldNames.isEmpty()) { | ||
log.warn("Ignoring validations {} for case [{}]: field names are identical with validation names", fieldNames, useCase.stringId) | ||
validationNames -= fieldNames | ||
} | ||
return validationNames | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. I think we can reduce this to:
Disadvantage is, we cannot have the same log message anymore. So the question is how important is the log message. |
||
} | ||
} |
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,58 @@ | ||
package com.netgrif.application.engine.startup | ||
|
||
import com.netgrif.application.engine.auth.service.interfaces.IUserService | ||
import com.netgrif.application.engine.validations.interfaces.IValidationService | ||
import com.netgrif.application.engine.workflow.domain.Case | ||
import com.netgrif.application.engine.workflow.domain.QCase | ||
import com.netgrif.application.engine.workflow.domain.repositories.CaseRepository | ||
import com.querydsl.core.types.Predicate | ||
import groovy.util.logging.Slf4j | ||
import org.springframework.beans.factory.annotation.Autowired | ||
import org.springframework.data.domain.PageRequest | ||
import org.springframework.stereotype.Component | ||
|
||
@Slf4j | ||
@Component | ||
class ValidationRunner extends AbstractOrderedCommandLineRunner { | ||
|
||
private static final int PAGE_SIZE = 100 | ||
public static final String VALIDATION_FILE_NAME = "engine-processes/validations/validation.xml" | ||
public static final String VALIDATION_PETRI_NET_IDENTIFIER = "validation" | ||
public static final String VALIDATION_ACTIVE_PLACE_ID = "active" | ||
public static final String VALIDATION_NAME_FIELD_ID = "name" | ||
public static final String VALIDATION_GROOVY_DEFINITION_FIELD_ID = "validation_definition_groovy" | ||
|
||
@Autowired | ||
private ImportHelper helper | ||
|
||
@Autowired | ||
private IUserService userService | ||
|
||
@Autowired | ||
private IValidationService validationService | ||
|
||
@Autowired | ||
private CaseRepository caseRepository | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. please use |
||
|
||
@Override | ||
void run(String... strings) throws Exception { | ||
log.info("Starting validation runner") | ||
|
||
helper.upsertNet(VALIDATION_FILE_NAME, VALIDATION_PETRI_NET_IDENTIFIER) | ||
Predicate predicate = QCase.case$.processIdentifier.eq(VALIDATION_PETRI_NET_IDENTIFIER) & QCase.case$.activePlaces.get(VALIDATION_ACTIVE_PLACE_ID).isNotNull() | ||
long numberActiveValidations = caseRepository.count(predicate) | ||
int pageCount = (int) (numberActiveValidations / PAGE_SIZE) + 1 | ||
pageCount.times { pageNum -> | ||
caseRepository.findAll(predicate, PageRequest.of(pageNum, PAGE_SIZE)) | ||
.getContent() | ||
.each { Case validationCase -> | ||
validationService.registerValidation( | ||
validationCase.getDataSet().get(VALIDATION_NAME_FIELD_ID).rawValue as String, | ||
validationCase.getDataSet().get(VALIDATION_GROOVY_DEFINITION_FIELD_ID).rawValue as String | ||
) | ||
} | ||
} | ||
|
||
log.info("Validation runner finished, [{}] validations successfully imported", numberActiveValidations) | ||
} | ||
} |
This file was deleted.
This file was deleted.
This file was deleted.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Is this needed? Can be used
DateUtils.parseDate()
instead?