From 68c56d70a4c873b55fdbb0f045ef915196593552 Mon Sep 17 00:00:00 2001 From: sgiannopoulos Date: Tue, 19 Dec 2023 14:54:17 +0200 Subject: [PATCH] new logic of validations --- .../commons/validation/BaseValidator.java | 137 +++++++++++-- .../commons/validation/ValidationFailure.java | 24 +++ .../commons/validation/ValidationResult.java | 34 ++++ .../{ModelValidator.java => Validator.java} | 5 +- .../commons/validation/ValidatorFactory.java | 33 ++++ .../NavigationDetailsSpecification.java | 16 ++ ...NavigationDetailsSpecificationBuilder.java | 53 +++++ .../NavigationReferenceSpecification.java | 15 ++ ...vigationReferenceSpecificationBuilder.java | 53 +++++ .../specification/PropertySpecification.java | 15 ++ .../PropertySpecificationBuilder.java | 62 ++++++ .../specification/Specification.java | 6 + .../DescriptionTemplateTypePersist.java | 110 ++++++----- .../GlobalExceptionHandler.java | 185 ++++++++++++++++-- .../ValidatorRequestBodyAdvice.java | 31 +-- .../src/main/resources/messages.properties | 5 +- 16 files changed, 684 insertions(+), 100 deletions(-) create mode 100644 dmp-backend/core/src/main/java/eu/eudat/commons/validation/ValidationFailure.java create mode 100644 dmp-backend/core/src/main/java/eu/eudat/commons/validation/ValidationResult.java rename dmp-backend/core/src/main/java/eu/eudat/commons/validation/{ModelValidator.java => Validator.java} (56%) create mode 100644 dmp-backend/core/src/main/java/eu/eudat/commons/validation/ValidatorFactory.java create mode 100644 dmp-backend/core/src/main/java/eu/eudat/commons/validation/specification/NavigationDetailsSpecification.java create mode 100644 dmp-backend/core/src/main/java/eu/eudat/commons/validation/specification/NavigationDetailsSpecificationBuilder.java create mode 100644 dmp-backend/core/src/main/java/eu/eudat/commons/validation/specification/NavigationReferenceSpecification.java create mode 100644 dmp-backend/core/src/main/java/eu/eudat/commons/validation/specification/NavigationReferenceSpecificationBuilder.java create mode 100644 dmp-backend/core/src/main/java/eu/eudat/commons/validation/specification/PropertySpecification.java create mode 100644 dmp-backend/core/src/main/java/eu/eudat/commons/validation/specification/PropertySpecificationBuilder.java create mode 100644 dmp-backend/core/src/main/java/eu/eudat/commons/validation/specification/Specification.java diff --git a/dmp-backend/core/src/main/java/eu/eudat/commons/validation/BaseValidator.java b/dmp-backend/core/src/main/java/eu/eudat/commons/validation/BaseValidator.java index 9f1773f23..1d8644200 100644 --- a/dmp-backend/core/src/main/java/eu/eudat/commons/validation/BaseValidator.java +++ b/dmp-backend/core/src/main/java/eu/eudat/commons/validation/BaseValidator.java @@ -1,47 +1,150 @@ package eu.eudat.commons.validation; +import eu.eudat.commons.validation.specification.*; +import eu.eudat.convention.ConventionService; import eu.eudat.errorcode.ErrorThesaurusProperties; import gr.cite.tools.exception.MyValidationException; import org.springframework.validation.BeanPropertyBindingResult; import org.springframework.validation.Errors; import org.springframework.validation.FieldError; +import org.springframework.validation.ValidationUtils; -import java.util.AbstractMap; -import java.util.ArrayList; -import java.util.List; -import java.util.Map; +import java.text.MessageFormat; +import java.util.*; -public abstract class BaseValidator implements ModelValidator { - private BeanPropertyBindingResult result; +public abstract class BaseValidator implements Validator { + private BeanPropertyBindingResult bindingResult; + protected final ConventionService conventionService; protected final ErrorThesaurusProperties errors; - protected BaseValidator(ErrorThesaurusProperties errors) { + protected BaseValidator(ConventionService conventionService, ErrorThesaurusProperties errors) { + this.conventionService = conventionService; this.errors = errors; } + protected abstract Class modelClass(); + + protected abstract List specifications(T item); + + @Override + public boolean supports(Class clazz) { + return clazz == null ? false : this.modelClass().equals(clazz); + } + + @Override + public void validate(Object obj, Errors e) { + if (obj == null) return; + T item = this.modelClass().cast(obj); + List specifications = this.specifications(item); + if (specifications == null) return;; + List propertySpecifications = specifications.stream().filter(x-> x instanceof PropertySpecification).map(x-> (PropertySpecification)x).toList(); + for (PropertySpecification propertySpecification : propertySpecifications){ + if ((propertySpecification.getPrecondition() == null || propertySpecification.getPrecondition().get()) && + (propertySpecification.getSpecification() == null || !propertySpecification.getSpecification().get()) + ){ + e.rejectValue(propertySpecification.getErrorKey(), propertySpecification.getErrorCode(), propertySpecification.getErrorMessage()); + } + } + + List navigationReferenceSpecifications = specifications.stream().filter(x-> x instanceof NavigationReferenceSpecification).map(x-> (NavigationReferenceSpecification)x).toList(); + for (NavigationReferenceSpecification navigationReferenceSpecification : navigationReferenceSpecifications){ + if (navigationReferenceSpecification.getReference() != null && (navigationReferenceSpecification.getPrecondition() == null || navigationReferenceSpecification.getPrecondition().get())){ + try { + e.pushNestedPath(navigationReferenceSpecification.getKey()); + ValidationUtils.invokeValidator(navigationReferenceSpecification.getValidator().get(), navigationReferenceSpecification.getReference(), e); + } finally { + e.popNestedPath(); + } + } + } + + List navigationDetailsSpecifications = specifications.stream().filter(x-> x instanceof NavigationDetailsSpecification).map(x-> (NavigationDetailsSpecification)x).toList(); + for (NavigationDetailsSpecification navigationDetailsSpecification : navigationDetailsSpecifications){ + if (navigationDetailsSpecification.getDetails() != null && (navigationDetailsSpecification.getPrecondition() == null || navigationDetailsSpecification.getPrecondition().get())){ + + for (int i = 0; i < navigationDetailsSpecification.getDetails().size(); i++) { + try { + e.pushNestedPath(MessageFormat.format("{0}[{1}]", navigationDetailsSpecification.getKey(), i)); + ValidationUtils.invokeValidator(navigationDetailsSpecification.getValidator().get(), navigationDetailsSpecification.getDetails().get(i), e); + } finally { + e.popNestedPath(); + } + } + } + } + } + @Override public void validate(Object target){ - this.result = new org.springframework.validation.BeanPropertyBindingResult(target, target.getClass().getName()); - this.validate(target, this.result); + this.bindingResult = new org.springframework.validation.BeanPropertyBindingResult(target, target.getClass().getName()); + this.validate(target, this.bindingResult); } @Override public void validateForce(Object target) { this.validate(target); - - if (this.result != null && result.hasErrors()){ - List>> errorsMap = new ArrayList<>(); - for (FieldError fieldError : result.getFieldErrors()){ - errorsMap.add(new AbstractMap.SimpleEntry<>(fieldError.getField(), List.of(fieldError.getDefaultMessage()))); - } + ValidationResult result = result(); + if (!result.isValid()){ + List>> errorsMap = this.flattenValidationResult(); throw new MyValidationException(this.errors.getModelValidation().getCode(), errorsMap); } } + + protected List>> flattenValidationResult() { + ValidationResult result = result(); + List>> errorsMap = new ArrayList<>(); + for (ValidationFailure fieldError : result.getErrors()){ + Map.Entry> entry = errorsMap.stream().filter(x-> Objects.equals(x.getKey(), fieldError.getErrorKey())).findFirst().orElse(null); + if (entry == null) { + entry = new AbstractMap.SimpleEntry<>(fieldError.getErrorKey(), new ArrayList<>()); + errorsMap.add(entry); + } + entry.getValue().add(fieldError.getErrorMessage()); + } + + return errorsMap; + } + + protected PropertySpecificationBuilder spec() { + return new PropertySpecificationBuilder(); + } + + protected NavigationReferenceSpecificationBuilder refSpec() { + return new NavigationReferenceSpecificationBuilder(); + } + + protected NavigationDetailsSpecificationBuilder navSpec() + { + return new NavigationDetailsSpecificationBuilder(); + } @Override - public Errors result() { - return this.result; + public ValidationResult result() { + ValidationResult validationResult = new ValidationResult(); + if (this.bindingResult != null && bindingResult.hasErrors()){ + for (FieldError fieldError : bindingResult.getFieldErrors()){ + validationResult.add(new ValidationFailure(fieldError.getField(), fieldError.getDefaultMessage())); + } + } + return validationResult; + } + + + + protected Boolean isValidGuid(UUID guid) { + return this.conventionService.isValidGuid(guid); + } + + protected Boolean isValidHash(String hash) { + return this.conventionService.isValidHash(hash); + } + + protected Boolean isEmpty(String value) { + return this.conventionService.isNullOrEmpty(value); + } + protected Boolean lessEqual(String value, int size) { + return value.length() <= size; } } diff --git a/dmp-backend/core/src/main/java/eu/eudat/commons/validation/ValidationFailure.java b/dmp-backend/core/src/main/java/eu/eudat/commons/validation/ValidationFailure.java new file mode 100644 index 000000000..9d20f56cb --- /dev/null +++ b/dmp-backend/core/src/main/java/eu/eudat/commons/validation/ValidationFailure.java @@ -0,0 +1,24 @@ +package eu.eudat.commons.validation; + +public class ValidationFailure { + public String errorKey; + + public String errorMessage; + + public ValidationFailure(String errorMessage) { + this.errorMessage = errorMessage; + } + + public ValidationFailure(String errorKey, String errorMessage) { + this.errorKey = errorKey; + this.errorMessage = errorMessage; + } + + public String getErrorKey() { + return errorKey; + } + + public String getErrorMessage() { + return errorMessage; + } +} diff --git a/dmp-backend/core/src/main/java/eu/eudat/commons/validation/ValidationResult.java b/dmp-backend/core/src/main/java/eu/eudat/commons/validation/ValidationResult.java new file mode 100644 index 000000000..a659d175f --- /dev/null +++ b/dmp-backend/core/src/main/java/eu/eudat/commons/validation/ValidationResult.java @@ -0,0 +1,34 @@ +package eu.eudat.commons.validation; + +import java.util.ArrayList; +import java.util.List; + +public class ValidationResult { + private final List errors; + + public ValidationResult() { + this.errors = new ArrayList<>(); + } + + public Boolean isValid() { + return this.errors.isEmpty(); + } + public ValidationResult add(ValidationFailure failure) { + if (failure != null) { + errors.add(failure); + } + return this; + } + + public ValidationResult addAll(List failures) { + if (failures != null) { + errors.addAll(failures); + } + return this; + } + + public List getErrors() { + return errors; + } +} + diff --git a/dmp-backend/core/src/main/java/eu/eudat/commons/validation/ModelValidator.java b/dmp-backend/core/src/main/java/eu/eudat/commons/validation/Validator.java similarity index 56% rename from dmp-backend/core/src/main/java/eu/eudat/commons/validation/ModelValidator.java rename to dmp-backend/core/src/main/java/eu/eudat/commons/validation/Validator.java index 9935d4618..a3db41f5b 100644 --- a/dmp-backend/core/src/main/java/eu/eudat/commons/validation/ModelValidator.java +++ b/dmp-backend/core/src/main/java/eu/eudat/commons/validation/Validator.java @@ -1,10 +1,9 @@ package eu.eudat.commons.validation; import org.springframework.validation.Errors; -import org.springframework.validation.Validator; -public interface ModelValidator extends Validator { +public interface Validator extends org.springframework.validation.Validator { void validate(Object target); void validateForce(Object target); - Errors result(); + ValidationResult result(); } diff --git a/dmp-backend/core/src/main/java/eu/eudat/commons/validation/ValidatorFactory.java b/dmp-backend/core/src/main/java/eu/eudat/commons/validation/ValidatorFactory.java new file mode 100644 index 000000000..9c3e844b7 --- /dev/null +++ b/dmp-backend/core/src/main/java/eu/eudat/commons/validation/ValidatorFactory.java @@ -0,0 +1,33 @@ +package eu.eudat.commons.validation; + +import org.springframework.context.ApplicationContext; +import org.springframework.stereotype.Service; + +@Service +public class ValidatorFactory { + + private final ApplicationContext applicationContext; + + public ValidatorFactory(ApplicationContext applicationContext) { + this.applicationContext = applicationContext; + } + + public T validator(Class type){ + + T item = this.applicationContext.getBean(type); + + if (item == null || !(item instanceof Validator)) throw new RuntimeException("unrecognized validator " + type.getSimpleName()); + + return item; + } + + public Validator validator(String validtor){ + + Validator item = this.applicationContext.getBean(validtor, Validator.class); + + if (item == null || !(item instanceof Validator)) throw new RuntimeException("unrecognized validator " + validtor); + + return item; + } + +} diff --git a/dmp-backend/core/src/main/java/eu/eudat/commons/validation/specification/NavigationDetailsSpecification.java b/dmp-backend/core/src/main/java/eu/eudat/commons/validation/specification/NavigationDetailsSpecification.java new file mode 100644 index 000000000..9630799fb --- /dev/null +++ b/dmp-backend/core/src/main/java/eu/eudat/commons/validation/specification/NavigationDetailsSpecification.java @@ -0,0 +1,16 @@ +package eu.eudat.commons.validation.specification; + +import eu.eudat.commons.validation.Validator; + +import java.util.List; +import java.util.function.Supplier; + +public interface NavigationDetailsSpecification extends Specification { + Supplier getPrecondition(); + + String getKey(); + + List getDetails(); + + Supplier getValidator(); +} diff --git a/dmp-backend/core/src/main/java/eu/eudat/commons/validation/specification/NavigationDetailsSpecificationBuilder.java b/dmp-backend/core/src/main/java/eu/eudat/commons/validation/specification/NavigationDetailsSpecificationBuilder.java new file mode 100644 index 000000000..1e9be5f18 --- /dev/null +++ b/dmp-backend/core/src/main/java/eu/eudat/commons/validation/specification/NavigationDetailsSpecificationBuilder.java @@ -0,0 +1,53 @@ +package eu.eudat.commons.validation.specification; + +import eu.eudat.commons.validation.Validator; + +import java.util.List; +import java.util.function.Supplier; + +public class NavigationDetailsSpecificationBuilder implements NavigationDetailsSpecification { + private Supplier precondition; + private String key; + private List details; + private Supplier validator; + + public NavigationDetailsSpecificationBuilder iff(Supplier value){ + this.precondition = value; + return this; + } + + public NavigationDetailsSpecificationBuilder on(String value){ + this.key = value; + return this; + } + + public NavigationDetailsSpecificationBuilder over(List value){ + this.details = value; + return this; + } + + public NavigationDetailsSpecificationBuilder using(Supplier value){ + this.validator = value; + return this; + } + + @Override + public Supplier getPrecondition() { + return precondition; + } + + @Override + public String getKey() { + return key; + } + + @Override + public List getDetails() { + return details; + } + + @Override + public Supplier getValidator() { + return validator; + } +} diff --git a/dmp-backend/core/src/main/java/eu/eudat/commons/validation/specification/NavigationReferenceSpecification.java b/dmp-backend/core/src/main/java/eu/eudat/commons/validation/specification/NavigationReferenceSpecification.java new file mode 100644 index 000000000..0ef62c71a --- /dev/null +++ b/dmp-backend/core/src/main/java/eu/eudat/commons/validation/specification/NavigationReferenceSpecification.java @@ -0,0 +1,15 @@ +package eu.eudat.commons.validation.specification; + +import eu.eudat.commons.validation.Validator; + +import java.util.function.Supplier; + +public interface NavigationReferenceSpecification extends Specification{ + Supplier getPrecondition(); + + String getKey(); + + Object getReference(); + + Supplier getValidator(); +} diff --git a/dmp-backend/core/src/main/java/eu/eudat/commons/validation/specification/NavigationReferenceSpecificationBuilder.java b/dmp-backend/core/src/main/java/eu/eudat/commons/validation/specification/NavigationReferenceSpecificationBuilder.java new file mode 100644 index 000000000..3f3465226 --- /dev/null +++ b/dmp-backend/core/src/main/java/eu/eudat/commons/validation/specification/NavigationReferenceSpecificationBuilder.java @@ -0,0 +1,53 @@ +package eu.eudat.commons.validation.specification; + +import eu.eudat.commons.validation.Validator; + +import java.util.function.Supplier; + +public class NavigationReferenceSpecificationBuilder implements NavigationReferenceSpecification { + private Supplier precondition; + private String key; + private Object reference; + private Supplier validator; + + public NavigationReferenceSpecificationBuilder iff(Supplier value){ + this.precondition = value; + return this; + } + + public NavigationReferenceSpecificationBuilder on(String value){ + this.key = value; + return this; + } + + public NavigationReferenceSpecificationBuilder over(Object value){ + this.reference = value; + return this; + } + + public NavigationReferenceSpecificationBuilder using(Supplier value){ + this.validator = value; + return this; + } + + @Override + public Supplier getPrecondition() { + return precondition; + } + + @Override + public String getKey() { + return key; + } + + @Override + public Object getReference() { + return reference; + } + + @Override + public Supplier getValidator() { + return validator; + } +} + diff --git a/dmp-backend/core/src/main/java/eu/eudat/commons/validation/specification/PropertySpecification.java b/dmp-backend/core/src/main/java/eu/eudat/commons/validation/specification/PropertySpecification.java new file mode 100644 index 000000000..12d317d68 --- /dev/null +++ b/dmp-backend/core/src/main/java/eu/eudat/commons/validation/specification/PropertySpecification.java @@ -0,0 +1,15 @@ +package eu.eudat.commons.validation.specification; + +import java.util.function.Supplier; + +public interface PropertySpecification extends Specification { + + Supplier getPrecondition(); + + Supplier getSpecification(); + + String getErrorKey(); + + String getErrorMessage(); + String getErrorCode(); +} diff --git a/dmp-backend/core/src/main/java/eu/eudat/commons/validation/specification/PropertySpecificationBuilder.java b/dmp-backend/core/src/main/java/eu/eudat/commons/validation/specification/PropertySpecificationBuilder.java new file mode 100644 index 000000000..9851d65c4 --- /dev/null +++ b/dmp-backend/core/src/main/java/eu/eudat/commons/validation/specification/PropertySpecificationBuilder.java @@ -0,0 +1,62 @@ +package eu.eudat.commons.validation.specification; + +import java.util.function.Supplier; + +public class PropertySpecificationBuilder implements PropertySpecification{ + private Supplier precondition; + private Supplier specification; + private String errorKey; + private String errorMessage; + private String errorCode = "validationerror"; + + public PropertySpecificationBuilder iff(Supplier value){ + this.precondition = value; + return this; + } + + public PropertySpecificationBuilder must(Supplier value){ + this.specification = value; + return this; + } + + public PropertySpecificationBuilder failOn(String value){ + this.errorKey = value; + return this; + } + + public PropertySpecificationBuilder failWith(String value){ + this.errorMessage = value; + return this; + } + + public PropertySpecificationBuilder failWithCode(String value){ + this.errorCode = value; + return this; + } + + @Override + public Supplier getPrecondition() { + return precondition; + } + + @Override + public Supplier getSpecification() { + return specification; + } + + @Override + public String getErrorKey() { + return errorKey; + } + + @Override + public String getErrorMessage() { + return errorMessage; + } + + @Override + public String getErrorCode() { + return errorCode; + } +} + diff --git a/dmp-backend/core/src/main/java/eu/eudat/commons/validation/specification/Specification.java b/dmp-backend/core/src/main/java/eu/eudat/commons/validation/specification/Specification.java new file mode 100644 index 000000000..de64a65f1 --- /dev/null +++ b/dmp-backend/core/src/main/java/eu/eudat/commons/validation/specification/Specification.java @@ -0,0 +1,6 @@ +package eu.eudat.commons.validation.specification; + +import java.util.function.Supplier; + +public interface Specification { +} diff --git a/dmp-backend/core/src/main/java/eu/eudat/model/persist/DescriptionTemplateTypePersist.java b/dmp-backend/core/src/main/java/eu/eudat/model/persist/DescriptionTemplateTypePersist.java index 54e8f7902..59b9bb274 100644 --- a/dmp-backend/core/src/main/java/eu/eudat/model/persist/DescriptionTemplateTypePersist.java +++ b/dmp-backend/core/src/main/java/eu/eudat/model/persist/DescriptionTemplateTypePersist.java @@ -2,25 +2,26 @@ package eu.eudat.model.persist; import eu.eudat.commons.enums.DescriptionTemplateTypeStatus; -import eu.eudat.commons.validation.BaseValidator; -import eu.eudat.commons.validation.FieldNotNullIfOtherSet; -import eu.eudat.commons.validation.ValidEnum; -import eu.eudat.commons.validation.ValidId; +import eu.eudat.commons.validation.*; +import eu.eudat.commons.validation.specification.Specification; +import eu.eudat.convention.ConventionService; import eu.eudat.data.DescriptionTemplateTypeEntity; import eu.eudat.errorcode.ErrorThesaurusProperties; import eu.eudat.model.Description; import jakarta.validation.constraints.NotEmpty; import jakarta.validation.constraints.NotNull; import jakarta.validation.constraints.Size; -import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.beans.factory.config.ConfigurableBeanFactory; import org.springframework.context.MessageSource; +import org.springframework.context.annotation.Scope; import org.springframework.context.i18n.LocaleContextHolder; import org.springframework.stereotype.Component; import org.springframework.validation.Errors; import org.springframework.validation.ValidationUtils; -import org.springframework.validation.Validator; +import java.util.ArrayList; +import java.util.Arrays; import java.util.List; import java.util.UUID; @@ -30,19 +31,29 @@ public class DescriptionTemplateTypePersist { @ValidId(message = "{validation.invalidid}") private UUID id; + public final static String _id = "id"; + @NotNull(message = "{validation.empty}") @NotEmpty(message = "{validation.empty}") @Size(max = DescriptionTemplateTypeEntity._nameLength, message = "{validation.largerthanmax}") private String name = null; + public final static String _name = "name"; + private String hash; + public final static String _hash = "hash"; + @ValidEnum(message = "{validation.empty}") private DescriptionTemplateTypeStatus status; private List nested; + public final static String _nested = "nested"; + private DescriptionTemplateTypePersist obj; + + public final static String _obj = "obj"; public UUID getId() { return id; @@ -93,62 +104,53 @@ public class DescriptionTemplateTypePersist { } @Component(DescriptionTemplateTypePersistValidator.ValidatorName) - public static class DescriptionTemplateTypePersistValidator extends BaseValidator { - - @Autowired - private DescriptionTemplateTypePersistValidator1 descriptionTemplateTypePersistValidator; + @Scope(value = ConfigurableBeanFactory.SCOPE_PROTOTYPE) + public static class DescriptionTemplateTypePersistValidator extends BaseValidator { public static final String ValidatorName = "DescriptionTemplateTypePersistValidator"; + private final ValidatorFactory validatorFactory; + private final MessageSource messageSource; - public DescriptionTemplateTypePersistValidator(MessageSource messageSource, ErrorThesaurusProperties errors) { - super(errors); + public DescriptionTemplateTypePersistValidator(MessageSource messageSource, ValidatorFactory validatorFactory, ConventionService conventionService, ErrorThesaurusProperties errors) { + super(conventionService, errors); this.messageSource = messageSource; + this.validatorFactory = validatorFactory; } - public boolean supports(Class clazz) { - return DescriptionTemplateTypePersist.class.equals(clazz); + @Override + protected Class modelClass() { + return DescriptionTemplateTypePersist.class; } - public void validate(Object obj, Errors e) { - e.rejectValue("name", "negativevalue", messageSource.getMessage("General_ItemNotFound", new Object[]{"aaa", Description.class.getSimpleName()}, LocaleContextHolder.getLocale())); - DescriptionTemplateTypePersist p = (DescriptionTemplateTypePersist) obj; - try { - e.pushNestedPath("obj"); - ValidationUtils.invokeValidator(descriptionTemplateTypePersistValidator, p.getObj(), e); - } finally { - e.popNestedPath(); - } - - - for (int i = 0; i < p.getNested().size(); i++) { - try { - e.pushNestedPath("nested["+i+"]"); - ValidationUtils.invokeValidator(descriptionTemplateTypePersistValidator, p.getObj(), e); - } finally { - e.popNestedPath(); - } - } - } - } - - @Component(DescriptionTemplateTypePersistValidator1.ValidatorName) - public static class DescriptionTemplateTypePersistValidator1 extends BaseValidator { - - public static final String ValidatorName = "DescriptionTemplateTypePersistValidator1"; - private final MessageSource messageSource; - - public DescriptionTemplateTypePersistValidator1(MessageSource messageSource, ErrorThesaurusProperties errors) { - super(errors); - this.messageSource = messageSource; - } - - public boolean supports(Class clazz) { - return DescriptionTemplateTypePersist.class.equals(clazz); - } - - public void validate(Object obj, Errors e) { - e.rejectValue("name", "negativevalue", messageSource.getMessage("General_ItemNotFound", new Object[]{"aaa", Description.class.getSimpleName()}, LocaleContextHolder.getLocale())); - + @Override + protected List specifications(DescriptionTemplateTypePersist item) { + return Arrays.asList( + this.spec() + .iff(() -> this.isValidGuid(item.getId())) + .must(() -> this.isValidHash(item.getHash())) + .failOn(DescriptionTemplateTypePersist._hash).failWith(messageSource.getMessage("Validation_Required", new Object[]{DescriptionTemplateTypePersist._hash}, LocaleContextHolder.getLocale())), + this.spec() + .iff(() -> !this.isValidGuid(item.getId())) + .must(() -> !this.isValidHash(item.getHash())) + .failOn(DescriptionTemplateTypePersist._hash).failWith(messageSource.getMessage("Validation_OverPosting", new Object[]{}, LocaleContextHolder.getLocale())), + this.spec() + .must(() -> !this.isEmpty(item.getName())) + .failOn(DescriptionTemplateTypePersist._name).failWith(messageSource.getMessage("Validation_Required", new Object[]{DescriptionTemplateTypePersist._name}, LocaleContextHolder.getLocale())), + this.spec() + .iff(() -> !this.isEmpty(item.getName())) + .must(() -> this.lessEqual(item.getName(), DescriptionTemplateTypeEntity._nameLength)) + .failOn(DescriptionTemplateTypePersist._name).failWith(messageSource.getMessage("Validation_MaxLength", new Object[]{DescriptionTemplateTypePersist._name}, LocaleContextHolder.getLocale())), + this.refSpec() + .iff(() -> item.getObj() != null) + .on(DescriptionTemplateTypePersist._obj) + .over(item.getObj()) + .using(() -> this.validatorFactory.validator(DescriptionTemplateTypePersistValidator.class)), + this.navSpec() + .iff(() -> item.getNested() != null) + .on(DescriptionTemplateTypePersist._nested) + .over(item.getNested()) + .using(() -> this.validatorFactory.validator(DescriptionTemplateTypePersistValidator.class)) + ); } } diff --git a/dmp-backend/web/src/main/java/eu/eudat/controllers/controllerhandler/GlobalExceptionHandler.java b/dmp-backend/web/src/main/java/eu/eudat/controllers/controllerhandler/GlobalExceptionHandler.java index 7223a7c57..a28826f3a 100644 --- a/dmp-backend/web/src/main/java/eu/eudat/controllers/controllerhandler/GlobalExceptionHandler.java +++ b/dmp-backend/web/src/main/java/eu/eudat/controllers/controllerhandler/GlobalExceptionHandler.java @@ -1,30 +1,191 @@ package eu.eudat.controllers.controllerhandler; -import eu.eudat.errorcode.ErrorThesaurusProperties; -import gr.cite.tools.exception.MyValidationException; +import eu.eudat.commons.JsonHandlingService; +import gr.cite.tools.exception.*; +import gr.cite.tools.logging.LoggerService; +import org.slf4j.LoggerFactory; import org.springframework.http.HttpStatus; import org.springframework.http.ResponseEntity; import org.springframework.web.bind.annotation.*; import org.springframework.web.context.request.WebRequest; +import java.text.MessageFormat; +import java.util.Map; + @RestControllerAdvice @ControllerAdvice public class GlobalExceptionHandler { + private static final LoggerService logger = new LoggerService(LoggerFactory.getLogger(GlobalExceptionHandler.class)); + + private final JsonHandlingService jsonHandlingService; - private final ErrorThesaurusProperties errors; - - public GlobalExceptionHandler(ErrorThesaurusProperties errors) { - this.errors = errors; + public GlobalExceptionHandler(JsonHandlingService jsonHandlingService) { + this.jsonHandlingService = jsonHandlingService; } + @ExceptionHandler(Exception.class) - public ResponseEntity handleUnexpectedErrors(Exception e, WebRequest request) throws Exception { - if (e instanceof MyValidationException argumentNotValidException) { - return new ResponseEntity<>(argumentNotValidException.getErrors(), HttpStatus.BAD_REQUEST); + public ResponseEntity handleUnexpectedErrors(Exception exception, WebRequest request) throws Exception { + HandledException handled = this.handleException(exception, request); + this.log(handled.getLevel(), exception, MessageFormat.format("returning code {0} and payload {1}", handled.getStatusCode(), handled.getMessage())); + return new ResponseEntity<>(handled.getMessage(), handled.getStatusCode()); + } + + public void log(System.Logger.Level level, Exception e, String message) { + if (level != null) { + switch (level) { + case TRACE: + logger.trace(message, e); + break; + case DEBUG: + logger.debug(message, e); + break; + case INFO: + logger.info(message, e); + break; + case WARNING: + logger.warn(message, e); + break; + case ERROR: + logger.error(message, e); + break; + } } - else { - throw e; + } + + public HandledException handleException(Exception exception, WebRequest request) throws Exception { + HttpStatus statusCode; + Map result; + System.Logger.Level logLevel; + + switch (exception){ + case MyNotFoundException myNotFoundException -> { + logLevel = System.Logger.Level.DEBUG; + statusCode = HttpStatus.NOT_FOUND; + int code = myNotFoundException.getCode(); + if (code > 0) { + result = Map.ofEntries( + Map.entry("code", code), + Map.entry("error", myNotFoundException.getMessage()) + ); + } + else { + result = Map.ofEntries( + Map.entry("error", myNotFoundException.getMessage()) + ); + } + } + case MyUnauthorizedException myUnauthorizedException -> { + logLevel = System.Logger.Level.DEBUG; + statusCode = HttpStatus.UNAUTHORIZED; + int code = myUnauthorizedException.getCode(); + if (code > 0) { + result = Map.ofEntries( + Map.entry("code", code), + Map.entry("error", myUnauthorizedException.getMessage()) + ); + } + else { + result = Map.ofEntries( + Map.entry("error", myUnauthorizedException.getMessage()) + ); + } + } + case MyForbiddenException myForbiddenException -> { + logLevel = System.Logger.Level.DEBUG; + statusCode = HttpStatus.FORBIDDEN; + int code = myForbiddenException.getCode(); + if (code > 0) { + result = Map.ofEntries( + Map.entry("code", code), + Map.entry("error", myForbiddenException.getMessage()) + ); + } + else { + result = Map.ofEntries( + Map.entry("error", myForbiddenException.getMessage()) + ); + } + } + case MyValidationException myValidationException -> { + logLevel = System.Logger.Level.DEBUG; + statusCode = HttpStatus.BAD_REQUEST; + int code = myValidationException.getCode(); + if (code > 0) { + result = Map.ofEntries( + Map.entry("code", code), + Map.entry("error", myValidationException.getMessage()), + Map.entry("message", myValidationException.getErrors()) + ); + } + else { + result = Map.ofEntries( + Map.entry("error", myValidationException.getMessage()), + Map.entry("message", myValidationException.getErrors()) + ); + } + } + case MyApplicationException myApplicationException -> { + logLevel = System.Logger.Level.ERROR; + statusCode = HttpStatus.INTERNAL_SERVER_ERROR; + int code = myApplicationException.getCode(); + if (code > 0) { + result = Map.ofEntries( + Map.entry("code", code), + Map.entry("error", myApplicationException.getMessage()) + ); + } + else { + result = Map.ofEntries( + Map.entry("error", myApplicationException.getMessage()) + ); + } + } + default -> { + logLevel = System.Logger.Level.ERROR; + statusCode = HttpStatus.INTERNAL_SERVER_ERROR; + result = Map.ofEntries( + Map.entry("error", "System error") + ); + } + }; + String serialization = this.jsonHandlingService.toJsonSafe(result); + return new HandledException(statusCode, serialization, logLevel); + } + + public static class HandledException{ + public HttpStatus statusCode; + public String message; + public System.Logger.Level level; + + public HandledException(HttpStatus statusCode, String message, System.Logger.Level level) { + this.statusCode = statusCode; + this.message = message; + this.level = level; } - } + public HttpStatus getStatusCode() { + return statusCode; + } + + public void setStatusCode(HttpStatus statusCode) { + this.statusCode = statusCode; + } + + public String getMessage() { + return message; + } + + public void setMessage(String message) { + this.message = message; + } + + public System.Logger.Level getLevel() { + return level; + } + + public void setLevel(System.Logger.Level level) { + this.level = level; + } + } } diff --git a/dmp-backend/web/src/main/java/eu/eudat/controllers/controllerhandler/ValidatorRequestBodyAdvice.java b/dmp-backend/web/src/main/java/eu/eudat/controllers/controllerhandler/ValidatorRequestBodyAdvice.java index f5a589b33..b8be7a9e9 100644 --- a/dmp-backend/web/src/main/java/eu/eudat/controllers/controllerhandler/ValidatorRequestBodyAdvice.java +++ b/dmp-backend/web/src/main/java/eu/eudat/controllers/controllerhandler/ValidatorRequestBodyAdvice.java @@ -1,10 +1,9 @@ package eu.eudat.controllers.controllerhandler; -import eu.eudat.commons.validation.BaseValidator; +import eu.eudat.commons.validation.Validator; +import eu.eudat.commons.validation.ValidatorFactory; import eu.eudat.commons.validation.ValidationFilterAnnotation; -import eu.eudat.logic.services.ApiContext; import gr.cite.tools.exception.MyApplicationException; -import org.springframework.beans.factory.annotation.Autowired; import org.springframework.core.MethodParameter; import org.springframework.http.HttpInputMessage; import org.springframework.http.converter.HttpMessageConverter; @@ -19,21 +18,27 @@ import java.util.Objects; @ControllerAdvice @RestControllerAdvice public class ValidatorRequestBodyAdvice implements RequestBodyAdvice { - @Autowired - private ApiContext apiContext; + private final ValidatorFactory validatorFactory; + + public ValidatorRequestBodyAdvice(ValidatorFactory validatorFactory) { + this.validatorFactory = validatorFactory; + } @Override public Object afterBodyRead(Object body, HttpInputMessage inputMessage, MethodParameter parameter, Type targetType, Class> converterType) { if (parameter.getMethod() != null) { - ValidationFilterAnnotation annotation = parameter.getMethod().getAnnotation((ValidationFilterAnnotation.class)); + ValidationFilterAnnotation[] annotations = parameter.getMethod().getAnnotationsByType((ValidationFilterAnnotation.class)); - if (annotation == null || !Objects.equals(parameter.getParameterName(), annotation.argumentName())) return body; + if (annotations == null) return body; + for (ValidationFilterAnnotation annotation : annotations){ + if (!Objects.equals(parameter.getParameterName(), annotation.argumentName())) continue; - BaseValidator validator = this.apiContext.getOperationsContext().getApplicationContext().getBean(annotation.validator(), BaseValidator.class); - if (validator == null) throw new MyApplicationException("validator not provided"); - - validator.validateForce(body); + Validator validator = validatorFactory.validator(annotation.validator()); + if (validator == null) throw new MyApplicationException("validator not provided"); + + validator.validateForce(body); + } } return body; } @@ -44,8 +49,8 @@ public class ValidatorRequestBodyAdvice implements RequestBodyAdvice { } @Override - public boolean supports(MethodParameter methodParameter, Type targetType, Class> converterType) { - return true; + public boolean supports(MethodParameter parameter, Type targetType, Class> converterType) { + return parameter.getMethod() != null && parameter.getMethod().isAnnotationPresent((ValidationFilterAnnotation.class)); } @Override diff --git a/dmp-backend/web/src/main/resources/messages.properties b/dmp-backend/web/src/main/resources/messages.properties index 7547f0630..5ff4a0394 100644 --- a/dmp-backend/web/src/main/resources/messages.properties +++ b/dmp-backend/web/src/main/resources/messages.properties @@ -16,4 +16,7 @@ validation.lowerthanmin=Value must be larger than {value} validation.largerthanmax=Value must be less than {value} validation.invalidid=Not valid id General_ItemNotFound=Item {0} of type {1} not found -Validation_Required={0} is required \ No newline at end of file +Validation_Required={0} is required +Validation_OverPosting=Too much info +Validation_MaxLength={0} too long +Validation_UnexpectedValue=Unexpected value in field {0} \ No newline at end of file