Hibernate complex unique constraint - java

I have an entity class with the following unique constraint setup:
#Table(name = "foo", uniqueConstraints = {
#UniqueConstraint(columnNames = {"service", "system", "priority", "is_default"})})
Where service and system are foreign keys for other entity classes, priority is an integer holding the priority of the entry among entries with the same service and system objects and is_default is a boolean indicating a default configuration entry.
This unique constraint almost does what I want to do, but what I would need is a setup where, if is_default is FALSE then there can be multiple entries with the same service and system keys just with different integer priority, while if is_default is TRUE then there can be only 1 entry with the given service and system keys, meaning there can be only 1 default entry for a given service and system. How could I achive such a constraint?

The key is to create your own validation annotation able to validate whether the row data is unique based on a particular criteria.
Let the service iterface extend UniqueValidated and implement the method that performs the validation
public interface UniqueValidated {
boolean isUnique(Object value, String fieldName) throws UnsupportedOperationException;
}
public interface FooService extends UniqueValidated {
// add, delete...
}
public class FooServiceImpl implements FooService {
// add, delete...
#Override
public boolean isUnique(Object value, String fieldName)
throws UnsupportedOperationException {
// the logic of validation itself, feel free to use DAO implementations
}
}
Create the annotation that you put over the mapped attribute.
#Target({ ElementType.METHOD, ElementType.FIELD, ElementType.ANNOTATION_TYPE
})
#Retention(RetentionPolicy.RUNTIME)
#Constraint(validatedBy = UniqueValidator.class)
#Documented
public #interface Unique {
String message() default "{validation.unique}";
Class<?>[] groups() default {};
Class<? extends Payload>[] payload() default {};
Class<? extends UniqueValidated> service();
String serviceQualifier() default "";
String fieldName();
}
Finally create the class handling with the annotation.
public class UniqueValidator implements ConstraintValidator<Unique, Object> {
#Autowired
private ApplicationContext applicationContext;
private UniqueValidated service;
private String fieldName;
#Override
public void initialize(Unique unique) {
Class<? extends UniqueValidated> clazz = unique.service();
this.fieldName = unique.fieldName();
this.service = this.applicationContext.getBean(clazz);
}
#Override
public boolean isValid(Object o, ConstraintValidatorContext constraintValidatorContext) {
if (this.service == null || this.service.isUnique(o, this.fieldName)) {
constraintValidatorContext.disableDefaultConstraintViolation();
return true;
}
return false;
}
}
I have got inspired with the tutorial available online on the JBoss Docs. This is quite complex structure, however it leads the result well. The biggest advantage is undoubtly you can make the custom unique validation over any service implementing the UniqueValidated. Anyway, you need to these snippets above customize for your project.
Mapping is simple:
#Unique(service = FooService.class, fieldName = "theNameOfThisField"
#Column(name = "...")
private String theNameOfThisField;

Related

Spring parse String to enum before entering the controller

I have the following controller:
public interface SaveController {
#PostMapping(value = "/save")
#ResponseStatus(code = HttpStatus.CREATED)
void save(#RequestBody #Valid SaveRequest saveRequest);
}
SaveRequest corresponds to:
public class SaveRequest {
#NotNull
private SaveType type;
private String name;
}
and SaveType:
public enum SaveType {
DAILY, WEEKLY, MONTHLY;
}
The controller does not receive the enum itself, but a camelCase String. I need to convert that String into the corresponding enum. For instance:
daily should become DAILY.
weekly should become WEEKLY.
monthly should become MONTHLY.
Any other String should become null.
I've tried using the Spring Converter class, which does not work when the enum is inside an object (at least I don't know how to make it work in such times).
I honestly don't know what else to try
https://www.baeldung.com/jackson-serialize-enums
This site should probably give you plenty of options.
Best is probably something like this:
public enum SaveType {
DAILY, WEEKLY, MONTHLY;
#JsonCreator
public static SaveType saveTypeforValue(String value) {
return SaveType.valueOf(value.toUpperCase());
}
}
What you require is to have custom annotation with a custom validation class for Enum.
javax.validation library doesn't have inbuilt support for enums.
Validation class
public class SaveTypeSubSetValidator implements ConstraintValidator<SaveTypeSubset, SaveType> {
private SaveType[] subset;
#Override
public void initialize(SaveTypeSubset constraint) {
this.subset = constraint.anyOf();
}
#Override
public boolean isValid(SaveType value, ConstraintValidatorContext context) {
return value == null || Arrays.asList(subset).contains(value);
}
}
interface for validation annotation with validation message
#Target({METHOD, FIELD, ANNOTATION_TYPE, CONSTRUCTOR, PARAMETER, TYPE_USE})
#Retention(RUNTIME)
#Documented
#Constraint(validatedBy = SaveTypeSubSetValidator.class)
public #interface SaveTypeSubset {
SaveType[] anyOf();
String message() default "must be any of {anyOf}";
Class<?>[] groups() default {};
Class<? extends Payload>[] payload() default {};
}
Usage
#SaveTypeSubset(anyOf = {SaveType.NEW, SaveType.OLD})
private SaveType SaveType;
This is one way. More ways are mentioned in this article.

Allow to override/set constraints validation for a child object for Hibernate validation

I have the following class :
class ContactInformation {
String phone;
String email;
}
which is used in the following classes :
class Person {
#Valid
ContactInformation contactInformation;
}
class Station {
#Valid
ContactInformation contactInformation;
}
The thing is that any instance of Person must have an email, but it is an optional information for Station. Do I have a way to define this at owner level to avoid duplicate the class ContactInformation ?
Instead of the field level validator you can add the Type level validator.
Steps:
Define Type level annotation
Write Validator for new annotation
Introduce your type with a new annotation
Define:
#Constraint(validatedBy = {PersonClassOptionalEmailValidator.class})
#Target({ElementType.TYPE})
#Retention(RetentionPolicy.RUNTIME)
public #interface PersonalEmailValid {
String message() default "Invalid Email address";
Class<?>[] groups() default {};
Class<? extends Payload>[] payload() default {};
}
Writing Custom Validator:
public static class PersonClassOptionalEmailValidator implements ConstraintValidator<PersonalEmailValid, Person> {
// Test Email validator, you should check prope regex for production
public static final String EMAIL_REGEX = "^[a-zA-Z0-9+_.-]+#[a-zA-Z0-9.-]+$";
private final Pattern pattern;
public PersonClassOptionalEmailValidator() {
pattern = Pattern.compile(EMAIL_REGEX);
}
#Override
public boolean isValid(Person person, ConstraintValidatorContext constraintValidatorContext) {
if (person.contactInformation != null) {
return pattern.matcher(person.contactInformation.email).matches();
}
return false;
}
}
Introduce new annotation to class
#Getter
#Setter
#NoArgsConstructor
#PersonalEmailValid
static class Person {
#Valid
ContactInformation contactInformation;
}
Reference
Gist

Is it possible to overload SpringBoot's ConstraintValidator isValid method?

Essentially, my service takes the role of streamlining the delivery of Email Notifications for standardization/control. I've therefore exposed a POST endpoint which takes in an email Bean as the response body which holds information such as receiver, sender, cc, etc and I would like to verify the fields of the incoming bean (i.e. email address format).
Currently, I have written a custom validator for validating a list of email addresses (#EmailAddresses). Is there a way to reuse the same validator to validate the email address for the "from" property which isn't a list as opposed to introducing another validator?
My Bean:
public class Email {
#JsonProperty("from")
private String from;
#EmailAddresses
#JsonProperty("to")
private List<String> to;
#EmailAddresses
#JsonProperty("cc")
private List<String> cc;
// some other fields
}
My Controller:
#RestController
public class MyController {
#RequestMapping(value = "/", method = RequestMethod.POST)
public String deliverEmailNotification(#Valid #RequestBody Email email) {
// something
}
}
My Custom Validation Annotation:
#Target({ElementType.ANNOTATION_TYPE, ElementType.FIELD, ElementType.PARAMETER})
#Retention(RetentionPolicy.RUNTIME)
#Documented
#Constraint(validatedBy = EmailAddressesValidator.class)
public #interface EmailAddresses {
String message() default "Must be a valid email";
Class<?>[] groups() default {};
Class<? extends Payload>[] payload() default {};
}
Validation Implementation:
public class EmailAddressesValidator implements
ConstraintValidator<EmailAddresses, List<String>> {
#Override
public void initialize(EmailAddresses emailAddresses) {
// do nothing
}
#Override
public boolean isValid(final List<String> emails, ConstraintValidatorContext constraintValidatorContext) {
// do something
}
}
So essentially I'm wondering whether is it possible to do something like this:
public class EmailAddressesValidator implements
ConstraintValidator<EmailAddresses, List<String>>, ConstraintValidator<EmailAddresses, String> {
#Override
public void initialize(EmailAddresses emailAddresses) {
// do nothing
}
#Override
public boolean isValid(final List<String> emails, ConstraintValidatorContext constraintValidatorContext) {
// do something
}
#Override
public boolean isValid(final String email, ConstraintValidatorContext constraintValidatorContext) {
// do something
}
}
Or is there another way around it?
Didn't manage to implement two instances of the ConstraintValidator due to Duplicate class error. However, I was able to achieve the equivalent of overloading by having the validation interface be validated by two implementation classes.
Based on the field type annotated with the validation annotation (#EmailAddress in this case), the respective validation implementation will kick in.
#Target({ElementType.ANNOTATION_TYPE, ElementType.CONSTRUCTOR, ElementType.METHOD, ElementType.FIELD, ElementType.PARAMETER})
#Retention(RetentionPolicy.RUNTIME)
#Documented
#Constraint(validatedBy = { EmailAddressValidator.class, EmailAddressesValidator.class })
public #interface EmailAddress {
String message() default "Must be a valid email";
Class<?>[] groups() default {};
Class<? extends Payload>[] payload() default {};
}
Validation implementation for List of Strings
public class EmailAddressesValidator implements ConstraintValidator<EmailAddress, List<String>> {
...
}
Validation implementation for String
public class EmailAddressValidator implements ConstraintValidator<EmailAddress, String> {
...
}

Validate at least one of three field in dto spring boot

I have a DTO that looks something like this:
class VehicleDto {
private String type;
private Car car;
private Bike bike;
}
Now depending on the type, I need to validate on at least one of Car and Bike.
Both cannot be present in the same request.
How can I do that?
Having two fields in class, while only one of them can present, seems like a design smell for me.
But if you insist on such design - you can create a custom Validator for your VehicleDto class.
public class VehicleValidator implements Validator {
public boolean supports(Class clazz) {
return VehicleDto.class.equals(clazz);
}
public void validate(Object obj, Errors errors) {
VehicleDto dto = (VehicleDto) obj;
ValidationUtils.rejectIfEmptyOrWhitespace(errors, "type",
"error.message.for.type.field");
if (null != dto.getType()
&& null != dto.getCar()
&& null != dto.getBike()) {
switch(dto.getType()) {
case "car":
errors.rejectValue("bike", "error.message.for.bike.field");
break;
case "bike":
errors.rejectValue("car", "error.message.for.car.field");
break;
}
}
}
}
Also, see Spring documentation about validation:
Validation using Spring’s Validator interface
Resolving codes to error messages
Injecting a Validator
For example, if we want to check whether my TaskDTO object is valid, by comparing its two attributes dueDate and repeatUntil , following are the steps to achieve it.
dependency in pom.xml:
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-validation</artifactId>
</dependency>
DTO class:
#ValidTaskDTO
public class TaskDTO {
#FutureOrPresent
private ZonedDateTime dueDate;
#NotBlank(message = "Title cannot be null or blank")
private String title;
private String description;
#NotNull
private RecurrenceType recurrenceType;
#Future
private ZonedDateTime repeatUntil;
}
Custom Annotation:
#Constraint(validatedBy = {TaskDTOValidator.class})
#Target({ElementType.TYPE})
#Retention(RetentionPolicy.RUNTIME)
public #interface ValidTaskDTO {
String message() default "Due date should not be greater than or equal to Repeat Until Date.";
Class<?>[] groups() default {};
Class<? extends Payload>[] payload() default {};
}
Constraint Validator:
public class TaskDTOValidator implements ConstraintValidator<ValidTaskDTO, TaskDTO> {
#Override
public void initialize(ValidTaskDTO constraintAnnotation) {
}
#Override
public boolean isValid(TaskDTO taskDTO, ConstraintValidatorContext constraintValidatorContext) {
if (taskDTO.getRecurrenceType() == RecurrenceType.NONE) {
return true;
}
return taskDTO.getRepeatUntil() != null && taskDTO.getDueDate().isBefore(taskDTO.getRepeatUntil());
}
}
Make sure that you have #Valid in front of RequestBody of a postmapping method in your RestController. Only then the validation will get invoked:
#PostMapping
public TaskReadDTO createTask(#Valid #RequestBody TaskDTO taskDTO) {
.....
}
I hope this helps. If you need a detailed explanation on steps, have a look at this video

Multiple constraint annotations confused on Java Bean Validation

I am confused about the case that have multiple constraint annotations on a field, below:
public class Student
{
#NotNull
#Size(min = 2, max = 14, message = "The name '${validatedValue}' must be between {min} and {max} characters long")
private String name;
public String getName()
{
return name;
}
public void setName(String name)
{
this.name = name;
}
}
Test case:
public class StudentTest
{
private static Validator validator;
#BeforeClass
public static void setUp()
{
ValidatorFactory factory = Validation.buildDefaultValidatorFactory();
validator = factory.getValidator();
System.out.println(Locale.getDefault());
}
#Test
public void nameTest()
{
Student student = new Student();
student.setName(null);
Set<ConstraintViolation<Student>> constraintViolations = validator.validateProperty(student, "name");
System.out.println(constraintViolations.size());
System.out.println(constraintViolations.iterator().next().getMessage());
}
}
The result is:
1
Can't be null
That is, when the #NotNull constraint is violated, it will not continue. Yes, this is the right situation. When one check is failed, we don't want it check the next constraint. But the situation is different when I used custom constraint.
I defined two custom constraints ACheck and BCheck.
#Target({ METHOD, FIELD, ANNOTATION_TYPE, CONSTRUCTOR, PARAMETER })
#Retention(RUNTIME)
#Documented
#Constraint(validatedBy = { ACheckValidator.class })
public #interface ACheck
{
String message() default "A check error";
Class<?>[] groups() default { };
Class<? extends Payload>[] payload() default { };
}
#Target({ METHOD, FIELD, ANNOTATION_TYPE, CONSTRUCTOR, PARAMETER })
#Retention(RUNTIME)
#Documented
#Constraint(validatedBy = { BCheckValidator.class })
public #interface BCheck
{
String message() default "B check error";
Class<?>[] groups() default { };
Class<? extends Payload>[] payload() default { };
}
public class ACheckValidator implements ConstraintValidator<ACheck, String>
{
public void initialize(ACheck constraintAnnotation)
{
}
public boolean isValid(String value, ConstraintValidatorContext context)
{
return false;
}
}
public class BCheckValidator implements ConstraintValidator<BCheck, String>
{
public void initialize(BCheck constraintAnnotation)
{
}
public boolean isValid(String value, ConstraintValidatorContext context)
{
return false;
}
}
There is not specific info about custom constraint, and I change the Student.java and use custom constraint like that:
#ACheck
#BCheck
private String name;
Test again, and the result is:
2
B check error
That is, when the #ACheck constraint is violatedm, it also wil check #BCheck, Why this happens, anything else I had ignored?
when the #NotNull constraint is violated, it will not continue
That is incorrect. It will continue checking all the other constraints. It's just that the Size validator considers a null value as an acceptable value. The reason is that typically, you want
a non-null, minimum size value: then you apply both constraints
or a nullable value, which must have a minimum size if the value is present: then you only apply Size.
You're misunderstanding those validators - they have no guarantee of order in which they are evaluated.
By default, constraints are evaluated in no particular order, regardless of which groups they belong to.
So that means that either your ACheck or your BCheck could have failed, or both; it's not determined which failure will occur first.
If you want to be able to define an ordering with two distinct annotations, then you would have to use a #GroupSequence to specify that.
Alternatively, if you want to fail fast, then configure the validator to do so.
Validator validator = Validation.byProvider( HibernateValidator.class )
.configure()
.failFast( true )
.buildValidatorFactory()
.getValidator();
I would personally discourage that approach as it implies that a user that fails validations must make repeated requests to the resource every time one thing is wrong, as opposed to getting everything that is wrong up front.

Categories