NoXss and length validation for notification entities

This commit is contained in:
ViacheslavKlimov 2023-04-13 12:53:59 +03:00
parent 5a57657479
commit e926854911
18 changed files with 115 additions and 75 deletions

View File

@ -103,7 +103,6 @@ import org.thingsboard.server.common.data.rpc.Rpc;
import org.thingsboard.server.common.data.rule.RuleChain; import org.thingsboard.server.common.data.rule.RuleChain;
import org.thingsboard.server.common.data.rule.RuleChainType; import org.thingsboard.server.common.data.rule.RuleChainType;
import org.thingsboard.server.common.data.rule.RuleNode; import org.thingsboard.server.common.data.rule.RuleNode;
import org.thingsboard.server.common.data.settings.UserDashboardAction;
import org.thingsboard.server.common.data.util.ThrowingBiFunction; import org.thingsboard.server.common.data.util.ThrowingBiFunction;
import org.thingsboard.server.common.data.widget.WidgetTypeDetails; import org.thingsboard.server.common.data.widget.WidgetTypeDetails;
import org.thingsboard.server.common.data.widget.WidgetsBundle; import org.thingsboard.server.common.data.widget.WidgetsBundle;
@ -130,12 +129,12 @@ import org.thingsboard.server.dao.queue.QueueService;
import org.thingsboard.server.dao.relation.RelationService; import org.thingsboard.server.dao.relation.RelationService;
import org.thingsboard.server.dao.rpc.RpcService; import org.thingsboard.server.dao.rpc.RpcService;
import org.thingsboard.server.dao.rule.RuleChainService; import org.thingsboard.server.dao.rule.RuleChainService;
import org.thingsboard.server.dao.service.ConstraintValidator;
import org.thingsboard.server.dao.service.Validator; import org.thingsboard.server.dao.service.Validator;
import org.thingsboard.server.dao.tenant.TbTenantProfileCache; import org.thingsboard.server.dao.tenant.TbTenantProfileCache;
import org.thingsboard.server.dao.tenant.TenantProfileService; import org.thingsboard.server.dao.tenant.TenantProfileService;
import org.thingsboard.server.dao.tenant.TenantService; import org.thingsboard.server.dao.tenant.TenantService;
import org.thingsboard.server.dao.user.UserService; import org.thingsboard.server.dao.user.UserService;
import org.thingsboard.server.dao.user.UserSettingsService;
import org.thingsboard.server.dao.widget.WidgetTypeService; import org.thingsboard.server.dao.widget.WidgetTypeService;
import org.thingsboard.server.dao.widget.WidgetsBundleService; import org.thingsboard.server.dao.widget.WidgetsBundleService;
import org.thingsboard.server.exception.ThingsboardErrorResponseHandler; import org.thingsboard.server.exception.ThingsboardErrorResponseHandler;
@ -164,7 +163,9 @@ import org.thingsboard.server.service.telemetry.TelemetrySubscriptionService;
import javax.mail.MessagingException; import javax.mail.MessagingException;
import javax.servlet.http.HttpServletResponse; import javax.servlet.http.HttpServletResponse;
import javax.validation.ConstraintViolation;
import java.util.List; import java.util.List;
import java.util.Objects;
import java.util.Optional; import java.util.Optional;
import java.util.Set; import java.util.Set;
import java.util.UUID; import java.util.UUID;
@ -395,16 +396,19 @@ public abstract class BaseController {
* Handles validation error for controller method arguments annotated with @{@link javax.validation.Valid} * Handles validation error for controller method arguments annotated with @{@link javax.validation.Valid}
* */ * */
@ExceptionHandler(MethodArgumentNotValidException.class) @ExceptionHandler(MethodArgumentNotValidException.class)
public void handleValidationError(MethodArgumentNotValidException e, HttpServletResponse response) { public void handleValidationError(MethodArgumentNotValidException validationError, HttpServletResponse response) {
String errorMessage = "Validation error: " + e.getFieldErrors().stream() List<ConstraintViolation<Object>> constraintsViolations = validationError.getFieldErrors().stream()
.map(fieldError -> { .map(fieldError -> {
String property = fieldError.getField(); try {
if (property.equals("valid") || StringUtils.endsWith(property, ".valid")) { // when custom @AssertTrue is used return (ConstraintViolation<Object>) fieldError.unwrap(ConstraintViolation.class);
property = ""; } catch (Exception e) {
log.warn("FieldError source is not of type ConstraintViolation");
return null; // should not happen
} }
return (!property.isEmpty() ? (property + " ") : "") + fieldError.getDefaultMessage();
}) })
.collect(Collectors.joining(", ")); .filter(Objects::nonNull)
.collect(Collectors.toList());
String errorMessage = "Validation error: " + ConstraintValidator.getErrorMessage(constraintsViolations);
ThingsboardException thingsboardException = new ThingsboardException(errorMessage, ThingsboardErrorCode.BAD_REQUEST_PARAMS); ThingsboardException thingsboardException = new ThingsboardException(errorMessage, ThingsboardErrorCode.BAD_REQUEST_PARAMS);
handleControllerException(thingsboardException, response); handleControllerException(thingsboardException, response);
} }

View File

@ -125,8 +125,10 @@ public class DefaultNotificationCenter extends AbstractSubscriptionService imple
NotificationRuleId ruleId = request.getRuleId(); NotificationRuleId ruleId = request.getRuleId();
notificationTemplate.getConfiguration().getDeliveryMethodsTemplates().forEach((deliveryMethod, template) -> { notificationTemplate.getConfiguration().getDeliveryMethodsTemplates().forEach((deliveryMethod, template) -> {
if (!template.isEnabled()) return; if (!template.isEnabled()) return;
if (!channels.get(deliveryMethod).check(tenantId)) { try {
throw new IllegalArgumentException("Unable to send notification via " + deliveryMethod.getName() + ": not configured or not working"); channels.get(deliveryMethod).check(tenantId);
} catch (Exception e) {
throw new IllegalArgumentException(e.getMessage());
} }
if (ruleId == null) { if (ruleId == null) {
if (targets.stream().noneMatch(target -> target.getConfiguration().getType().getSupportedDeliveryMethods().contains(deliveryMethod))) { if (targets.stream().noneMatch(target -> target.getConfiguration().getType().getSupportedDeliveryMethods().contains(deliveryMethod))) {
@ -341,14 +343,20 @@ public class DefaultNotificationCenter extends AbstractSubscriptionService imple
@Override @Override
public Set<NotificationDeliveryMethod> getAvailableDeliveryMethods(TenantId tenantId) { public Set<NotificationDeliveryMethod> getAvailableDeliveryMethods(TenantId tenantId) {
return channels.values().stream() return channels.values().stream()
.filter(channel -> channel.check(tenantId)) .filter(channel -> {
try {
channel.check(tenantId);
return true;
} catch (Exception e) {
return false;
}
})
.map(NotificationChannel::getDeliveryMethod) .map(NotificationChannel::getDeliveryMethod)
.collect(Collectors.toSet()); .collect(Collectors.toSet());
} }
@Override @Override
public boolean check(TenantId tenantId) { public void check(TenantId tenantId) throws Exception {
return true;
} }
@Override @Override

View File

@ -48,12 +48,11 @@ public class EmailNotificationChannel implements NotificationChannel<User, Email
} }
@Override @Override
public boolean check(TenantId tenantId) { public void check(TenantId tenantId) throws Exception {
try { try {
mailService.testConnection(tenantId); mailService.testConnection(tenantId);
return true;
} catch (Exception e) { } catch (Exception e) {
return false; throw new RuntimeException("Mail server is not available");
} }
} }

View File

@ -26,7 +26,7 @@ public interface NotificationChannel<R extends NotificationRecipient, T extends
ListenableFuture<Void> sendNotification(R recipient, T processedTemplate, NotificationProcessingContext ctx); ListenableFuture<Void> sendNotification(R recipient, T processedTemplate, NotificationProcessingContext ctx);
boolean check(TenantId tenantId); void check(TenantId tenantId) throws Exception;
NotificationDeliveryMethod getDeliveryMethod(); NotificationDeliveryMethod getDeliveryMethod();

View File

@ -22,12 +22,12 @@ import org.thingsboard.rule.engine.api.slack.SlackService;
import org.thingsboard.server.common.data.id.TenantId; import org.thingsboard.server.common.data.id.TenantId;
import org.thingsboard.server.common.data.notification.NotificationDeliveryMethod; import org.thingsboard.server.common.data.notification.NotificationDeliveryMethod;
import org.thingsboard.server.common.data.notification.settings.NotificationSettings; import org.thingsboard.server.common.data.notification.settings.NotificationSettings;
import org.thingsboard.server.dao.notification.NotificationSettingsService;
import org.thingsboard.server.service.notification.NotificationProcessingContext;
import org.thingsboard.server.common.data.notification.settings.SlackNotificationDeliveryMethodConfig; import org.thingsboard.server.common.data.notification.settings.SlackNotificationDeliveryMethodConfig;
import org.thingsboard.server.common.data.notification.targets.slack.SlackConversation; import org.thingsboard.server.common.data.notification.targets.slack.SlackConversation;
import org.thingsboard.server.common.data.notification.template.SlackDeliveryMethodNotificationTemplate; import org.thingsboard.server.common.data.notification.template.SlackDeliveryMethodNotificationTemplate;
import org.thingsboard.server.dao.notification.NotificationSettingsService;
import org.thingsboard.server.service.executors.ExternalCallExecutorService; import org.thingsboard.server.service.executors.ExternalCallExecutorService;
import org.thingsboard.server.service.notification.NotificationProcessingContext;
@Component @Component
@RequiredArgsConstructor @RequiredArgsConstructor
@ -47,9 +47,11 @@ public class SlackNotificationChannel implements NotificationChannel<SlackConver
} }
@Override @Override
public boolean check(TenantId tenantId) { public void check(TenantId tenantId) throws Exception {
NotificationSettings notificationSettings = notificationSettingsService.findNotificationSettings(tenantId); NotificationSettings notificationSettings = notificationSettingsService.findNotificationSettings(tenantId);
return notificationSettings.getDeliveryMethodsConfigs().containsKey(NotificationDeliveryMethod.SLACK); if (!notificationSettings.getDeliveryMethodsConfigs().containsKey(NotificationDeliveryMethod.SLACK)) {
throw new RuntimeException("Slack API token is not configured");
}
} }
@Override @Override

View File

@ -49,8 +49,10 @@ public class SmsNotificationChannel implements NotificationChannel<User, SmsDeli
} }
@Override @Override
public boolean check(TenantId tenantId) { public void check(TenantId tenantId) throws Exception {
return smsService.isConfigured(tenantId); if (!smsService.isConfigured(tenantId)) {
throw new RuntimeException("SMS provider is not configured");
}
} }
@Override @Override

View File

@ -27,6 +27,7 @@ import org.thingsboard.server.common.data.id.NotificationTemplateId;
import org.thingsboard.server.common.data.id.TenantId; import org.thingsboard.server.common.data.id.TenantId;
import org.thingsboard.server.common.data.notification.rule.trigger.NotificationRuleTriggerConfig; import org.thingsboard.server.common.data.notification.rule.trigger.NotificationRuleTriggerConfig;
import org.thingsboard.server.common.data.notification.rule.trigger.NotificationRuleTriggerType; import org.thingsboard.server.common.data.notification.rule.trigger.NotificationRuleTriggerType;
import org.thingsboard.server.common.data.validation.Length;
import org.thingsboard.server.common.data.validation.NoXss; import org.thingsboard.server.common.data.validation.NoXss;
import javax.validation.Valid; import javax.validation.Valid;
@ -43,6 +44,7 @@ public class NotificationRule extends BaseData<NotificationRuleId> implements Ha
private TenantId tenantId; private TenantId tenantId;
@NotBlank @NotBlank
@NoXss @NoXss
@Length(max = 255, message = "cannot be longer than 255 chars")
private String name; private String name;
@NotNull @NotNull
private NotificationTemplateId templateId; private NotificationTemplateId templateId;

View File

@ -22,6 +22,7 @@ import org.thingsboard.server.common.data.HasName;
import org.thingsboard.server.common.data.HasTenantId; import org.thingsboard.server.common.data.HasTenantId;
import org.thingsboard.server.common.data.id.NotificationTargetId; import org.thingsboard.server.common.data.id.NotificationTargetId;
import org.thingsboard.server.common.data.id.TenantId; import org.thingsboard.server.common.data.id.TenantId;
import org.thingsboard.server.common.data.validation.Length;
import org.thingsboard.server.common.data.validation.NoXss; import org.thingsboard.server.common.data.validation.NoXss;
import javax.validation.Valid; import javax.validation.Valid;
@ -35,6 +36,7 @@ public class NotificationTarget extends BaseData<NotificationTargetId> implement
private TenantId tenantId; private TenantId tenantId;
@NotBlank @NotBlank
@NoXss @NoXss
@Length(max = 255, message = "cannot be longer than 255 chars")
private String name; private String name;
@NotNull @NotNull
@Valid @Valid

View File

@ -23,6 +23,7 @@ import com.fasterxml.jackson.annotation.JsonTypeInfo;
import lombok.Data; import lombok.Data;
import org.thingsboard.server.common.data.notification.targets.platform.PlatformUsersNotificationTargetConfig; import org.thingsboard.server.common.data.notification.targets.platform.PlatformUsersNotificationTargetConfig;
import org.thingsboard.server.common.data.notification.targets.slack.SlackNotificationTargetConfig; import org.thingsboard.server.common.data.notification.targets.slack.SlackNotificationTargetConfig;
import org.thingsboard.server.common.data.validation.Length;
import org.thingsboard.server.common.data.validation.NoXss; import org.thingsboard.server.common.data.validation.NoXss;
@JsonIgnoreProperties(ignoreUnknown = true) @JsonIgnoreProperties(ignoreUnknown = true)
@ -35,6 +36,7 @@ import org.thingsboard.server.common.data.validation.NoXss;
public abstract class NotificationTargetConfig { public abstract class NotificationTargetConfig {
@NoXss @NoXss
@Length(max = 500, message = "cannot be longer than 500 chars")
private String description; private String description;
@JsonIgnore @JsonIgnore

View File

@ -21,6 +21,8 @@ import lombok.NoArgsConstructor;
import lombok.ToString; import lombok.ToString;
import org.apache.commons.lang3.StringUtils; import org.apache.commons.lang3.StringUtils;
import org.thingsboard.server.common.data.notification.NotificationDeliveryMethod; import org.thingsboard.server.common.data.notification.NotificationDeliveryMethod;
import org.thingsboard.server.common.data.validation.Length;
import org.thingsboard.server.common.data.validation.NoXss;
import javax.validation.constraints.NotEmpty; import javax.validation.constraints.NotEmpty;
@ -30,6 +32,8 @@ import javax.validation.constraints.NotEmpty;
@ToString(callSuper = true) @ToString(callSuper = true)
public class EmailDeliveryMethodNotificationTemplate extends DeliveryMethodNotificationTemplate implements HasSubject { public class EmailDeliveryMethodNotificationTemplate extends DeliveryMethodNotificationTemplate implements HasSubject {
@NoXss(fieldName = "email subject")
@Length(fieldName = "email subject", max = 250, message = "cannot be longer than 250 chars")
@NotEmpty @NotEmpty
private String subject; private String subject;

View File

@ -23,6 +23,7 @@ import org.thingsboard.server.common.data.HasTenantId;
import org.thingsboard.server.common.data.id.NotificationTemplateId; import org.thingsboard.server.common.data.id.NotificationTemplateId;
import org.thingsboard.server.common.data.id.TenantId; import org.thingsboard.server.common.data.id.TenantId;
import org.thingsboard.server.common.data.notification.NotificationType; import org.thingsboard.server.common.data.notification.NotificationType;
import org.thingsboard.server.common.data.validation.Length;
import org.thingsboard.server.common.data.validation.NoXss; import org.thingsboard.server.common.data.validation.NoXss;
import javax.validation.Valid; import javax.validation.Valid;
@ -36,6 +37,7 @@ public class NotificationTemplate extends BaseData<NotificationTemplateId> imple
private TenantId tenantId; private TenantId tenantId;
@NoXss @NoXss
@NotEmpty @NotEmpty
@Length(max = 255, message = "cannot be longer than 255 chars")
private String name; private String name;
@NoXss @NoXss
@NotNull @NotNull

View File

@ -1,30 +0,0 @@
/**
* Copyright © 2016-2023 The Thingsboard Authors
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package org.thingsboard.server.common.data.notification.template;
import lombok.AllArgsConstructor;
import lombok.Data;
import lombok.NoArgsConstructor;
@Data
@AllArgsConstructor
@NoArgsConstructor
public class NotificationText {
private String body;
private String subject;
}

View File

@ -20,6 +20,7 @@ import lombok.EqualsAndHashCode;
import lombok.NoArgsConstructor; import lombok.NoArgsConstructor;
import lombok.ToString; import lombok.ToString;
import org.thingsboard.server.common.data.notification.NotificationDeliveryMethod; import org.thingsboard.server.common.data.notification.NotificationDeliveryMethod;
import org.thingsboard.server.common.data.validation.NoXss;
@Data @Data
@NoArgsConstructor @NoArgsConstructor
@ -31,6 +32,12 @@ public class SlackDeliveryMethodNotificationTemplate extends DeliveryMethodNotif
super(other); super(other);
} }
@NoXss(fieldName = "Slack message")
@Override
public String getBody() {
return super.getBody();
}
@Override @Override
public NotificationDeliveryMethod getMethod() { public NotificationDeliveryMethod getMethod() {
return NotificationDeliveryMethod.SLACK; return NotificationDeliveryMethod.SLACK;

View File

@ -20,6 +20,8 @@ import lombok.EqualsAndHashCode;
import lombok.NoArgsConstructor; import lombok.NoArgsConstructor;
import lombok.ToString; import lombok.ToString;
import org.thingsboard.server.common.data.notification.NotificationDeliveryMethod; import org.thingsboard.server.common.data.notification.NotificationDeliveryMethod;
import org.thingsboard.server.common.data.validation.Length;
import org.thingsboard.server.common.data.validation.NoXss;
@Data @Data
@NoArgsConstructor @NoArgsConstructor
@ -31,6 +33,13 @@ public class SmsDeliveryMethodNotificationTemplate extends DeliveryMethodNotific
super(other); super(other);
} }
@NoXss(fieldName = "SMS message")
@Length(fieldName = "SMS message", max = 320, message = "cannot be longer than 320 chars")
@Override
public String getBody() {
return super.getBody();
}
@Override @Override
public NotificationDeliveryMethod getMethod() { public NotificationDeliveryMethod getMethod() {
return NotificationDeliveryMethod.SMS; return NotificationDeliveryMethod.SMS;

View File

@ -25,6 +25,8 @@ import lombok.NoArgsConstructor;
import lombok.ToString; import lombok.ToString;
import org.apache.commons.lang3.StringUtils; import org.apache.commons.lang3.StringUtils;
import org.thingsboard.server.common.data.notification.NotificationDeliveryMethod; import org.thingsboard.server.common.data.notification.NotificationDeliveryMethod;
import org.thingsboard.server.common.data.validation.Length;
import org.thingsboard.server.common.data.validation.NoXss;
import javax.validation.constraints.NotEmpty; import javax.validation.constraints.NotEmpty;
import java.util.Optional; import java.util.Optional;
@ -35,6 +37,8 @@ import java.util.Optional;
@ToString(callSuper = true) @ToString(callSuper = true)
public class WebDeliveryMethodNotificationTemplate extends DeliveryMethodNotificationTemplate implements HasSubject { public class WebDeliveryMethodNotificationTemplate extends DeliveryMethodNotificationTemplate implements HasSubject {
@NoXss(fieldName = "web notification subject")
@Length(fieldName = "web notification subject", max = 150, message = "cannot be longer than 150 chars")
@NotEmpty @NotEmpty
private String subject; private String subject;
private JsonNode additionalConfig; private JsonNode additionalConfig;
@ -45,6 +49,15 @@ public class WebDeliveryMethodNotificationTemplate extends DeliveryMethodNotific
this.additionalConfig = other.additionalConfig != null ? other.additionalConfig.deepCopy() : null; this.additionalConfig = other.additionalConfig != null ? other.additionalConfig.deepCopy() : null;
} }
@NoXss(fieldName = "web notification message")
@Length(fieldName = "web notification message", max = 250, message = "cannot be longer than 250 chars")
@Override
public String getBody() {
return super.getBody();
}
@NoXss(fieldName = "web notification button text")
@Length(fieldName = "web notification button text", max = 50, message = "cannot be longer than 50 chars")
@JsonIgnore @JsonIgnore
public String getButtonText() { public String getButtonText() {
return getButtonConfigProperty("text"); return getButtonConfigProperty("text");
@ -57,6 +70,8 @@ public class WebDeliveryMethodNotificationTemplate extends DeliveryMethodNotific
}); });
} }
@NoXss(fieldName = "web notification button link")
@Length(fieldName = "web notification button link", max = 300, message = "cannot be longer than 300 chars")
@JsonIgnore @JsonIgnore
public String getButtonLink() { public String getButtonLink() {
return getButtonConfigProperty("link"); return getButtonConfigProperty("link");

View File

@ -23,12 +23,12 @@ import java.lang.annotation.RetentionPolicy;
import java.lang.annotation.Target; import java.lang.annotation.Target;
@Retention(RetentionPolicy.RUNTIME) @Retention(RetentionPolicy.RUNTIME)
@Target(ElementType.FIELD) @Target({ElementType.FIELD, ElementType.METHOD})
@Constraint(validatedBy = {}) @Constraint(validatedBy = {})
public @interface Length { public @interface Length {
String message() default "length must be equal or less than {max}"; String message() default "length must be equal or less than {max}";
String fieldName(); String fieldName() default "";
int max() default 255; int max() default 255;

View File

@ -23,12 +23,16 @@ import java.lang.annotation.RetentionPolicy;
import java.lang.annotation.Target; import java.lang.annotation.Target;
@Retention(RetentionPolicy.RUNTIME) @Retention(RetentionPolicy.RUNTIME)
@Target(ElementType.FIELD) @Target({ElementType.FIELD, ElementType.METHOD})
@Constraint(validatedBy = {}) @Constraint(validatedBy = {})
public @interface NoXss { public @interface NoXss {
String message() default "is malformed"; String message() default "is malformed";
String fieldName() default "";
Class<?>[] groups() default {}; Class<?>[] groups() default {};
Class<? extends Payload>[] payload() default {}; Class<? extends Payload>[] payload() default {};
} }

View File

@ -17,6 +17,7 @@ package org.thingsboard.server.dao.service;
import com.google.common.collect.Iterators; import com.google.common.collect.Iterators;
import lombok.extern.slf4j.Slf4j; import lombok.extern.slf4j.Slf4j;
import org.apache.commons.lang3.StringUtils;
import org.hibernate.validator.HibernateValidator; import org.hibernate.validator.HibernateValidator;
import org.hibernate.validator.HibernateValidatorConfiguration; import org.hibernate.validator.HibernateValidatorConfiguration;
import org.hibernate.validator.cfg.ConstraintMapping; import org.hibernate.validator.cfg.ConstraintMapping;
@ -29,11 +30,13 @@ import org.thingsboard.server.common.data.validation.Length;
import org.thingsboard.server.common.data.validation.NoXss; import org.thingsboard.server.common.data.validation.NoXss;
import org.thingsboard.server.dao.exception.DataValidationException; import org.thingsboard.server.dao.exception.DataValidationException;
import javax.validation.Path; import javax.validation.ConstraintViolation;
import javax.validation.Validation; import javax.validation.Validation;
import javax.validation.Validator; import javax.validation.Validator;
import javax.validation.constraints.AssertTrue; import javax.validation.constraints.AssertTrue;
import java.util.List; import javax.validation.metadata.ConstraintDescriptor;
import java.util.Collection;
import java.util.Set;
import java.util.stream.Collectors; import java.util.stream.Collectors;
@Slf4j @Slf4j
@ -51,26 +54,31 @@ public class ConstraintValidator {
} }
public static void validateFields(Object data, String errorPrefix) { public static void validateFields(Object data, String errorPrefix) {
List<String> constraintsViolations = getConstraintsViolations(data); Set<ConstraintViolation<Object>> constraintsViolations = fieldsValidator.validate(data);
if (!constraintsViolations.isEmpty()) { if (!constraintsViolations.isEmpty()) {
throw new DataValidationException(errorPrefix + String.join(", ", constraintsViolations)); throw new DataValidationException(errorPrefix + getErrorMessage(constraintsViolations));
} }
} }
public static List<String> getConstraintsViolations(Object data) { public static String getErrorMessage(Collection<ConstraintViolation<Object>> constraintsViolations) {
return fieldsValidator.validate(data).stream() return constraintsViolations.stream()
.map(constraintViolation -> { .map(ConstraintValidator::getErrorMessage)
String property; .distinct().sorted().collect(Collectors.joining(", "));
if (constraintViolation.getConstraintDescriptor().getAttributes().containsKey("fieldName")) {
property = constraintViolation.getConstraintDescriptor().getAttributes().get("fieldName").toString();
} else {
Path propertyPath = constraintViolation.getPropertyPath();
property = Iterators.getLast(propertyPath.iterator()).toString();
} }
return property + " " + constraintViolation.getMessage();
}) public static String getErrorMessage(ConstraintViolation<Object> constraintViolation) {
.distinct() ConstraintDescriptor<?> constraintDescriptor = constraintViolation.getConstraintDescriptor();
.collect(Collectors.toList()); String property = (String) constraintDescriptor.getAttributes().get("fieldName");
if (StringUtils.isEmpty(property) && !(constraintDescriptor.getAnnotation() instanceof AssertTrue)) {
property = Iterators.getLast(constraintViolation.getPropertyPath().iterator()).toString();
}
String error = "";
if (StringUtils.isNotEmpty(property)) {
error += property + " ";
}
error += constraintViolation.getMessage();
return error;
} }
private static void initializeValidators() { private static void initializeValidators() {