My Custom Validation annotation looks like this:
#ConstraintComposition(AND)
#NotNull
#Pattern(regexp = "[A-Z]{4}", message = "Invalid")
#Target({ElementType.FIELD})
#Retention(RetentionPolicy.RUNTIME)
#Constraint(validatedBy = {})
public #interface ValidChars {
Since I don't have any class in the validatedBy section, I am not sure how to write unit test for this annotation.
Can someone please help?
You will need to create a JUnit test for a class that uses your ValidChars interface. However, as validatedBy is empty, it means that there is no validation implementation class that is associated with your constraint annotation.
So your test will be to verify that the constraint annotation is being applied correctly to some class, but you won't be able to test the "actual" validation logic.
In your test case, you can make use of the Bean Validation API (javax.validation.*). You can use validate of Validator, it'll check if the constraints specified in your ValidChars are being satisfied by the instance being validated.
The test then just asserts any constraint violation in the validation result..
e.g.
// YourEntity.java
private class YourEntity
{
#ValidChars
String name;
public String getName()
{
return name;
}
public void setName( String name )
{
this.name = name;
}
}
// YourEntityTest.java
#Test
void testInvalidCharsShouldContainViolations()
{
// given
ValidatorFactory factory = Validation.buildDefaultValidatorFactory();
Validator validator = factory.getValidator();
YourEntity yourEntity = new YourEntity();
// when
yourEntity.setName( "some-invalid-input" );
// then
Set<ConstraintViolation<YourEntity>> violations = validator.validate( yourEntity );
assertFalse( violations.isEmpty() );
}
Related
My goal is to create class level annotations, but I keep getting this error. For some reason, reflection isn't working when I use getClass to get the information of the annotation. I tried using aspects, but I'm not sure how to make spring boot aspect annotations class level.
org.bson.codecs.configuration.CodecConfigurationException: Can't find a codec for class java.lang.Class.
Below are the classes I used
#Retention(RetentionPolicy.RUNTIME)
#Documented
#Target(ElementType.TYPE)
public #interface ValidateInput {
short minLength();
short maxLength();
String regex() default "";
}
public abstract class SimpleValidation {
private String text;
private ValidateInput info = this.getClass().getAnnotation(ValidateInput.class);
protected SimpleValidation(String text) {
this.text = text;
}
}
#Value
#EqualsAndHashCode(callSuper = true)
#ValidateInput(maxLength = 50, minLength = 5, regex = "^[a-zA-Z0-9_!#$%&'*+/=?`{|}~^.-]+#[a-zA-Z0-9.-]+$")
public class Email extends SimpleValidation {
String email;
public Email(String email) {
super(email);
this.email = email;
}
}
I think you are trying to recreate the Bean validation behaviour which is already something standard, try to use it, as it will be easier than creating a heavy hierarchy of classes for achieving that.
Spring will be able to verify your validation automatically (After deserializing a JSON body, before inserting in DB..)
I am using spring data neo4j 6.1.3 and following is my use case code snippets
Domain Entity
#Data
#Node("DATspace")
public class DatSpace {
#Id #GeneratedValue
private Long neoId;
#Property("SUPtitle")
private String title;
private String SUPid;
}
Test class
#SpringBootTest
#EnableNeo4jRepositories(basePackages = "com.rahal.marvel")
public class ProjectionTest {
#Autowired
private Neo4jTemplate neo4jTemplate;
interface DATspaceProjection {
String getTitle();
String getSUPid();
}
#Test
public void test_projection(){
DatSpace d = neo4jTemplate.findOne("MATCH (s:DATspace {SUPid: $id}) RETURN s", Collections.singletonMap("id", "SPC_ML7"), DatSpace.class).get();
d.setTitle("title modified");
d.setSUPid("SUPid modified");
DATspaceProjection p = neo4jTemplate.saveAs(d, DATspaceProjection.class);
}
}
Ideally above saveAs function should modify both DATspace.SUPtitle and DATspace.SUPid. However it only modify SUPid but not SUPtitle. I presume it is due to property mapping (#Property) . Is this a bug or are there any workaround?
The provided #Property annotation does only have an impact on the annotated property (title) itself.
There is no knowledge right now that goes from the getTitle() method in the projection to the annotated title field in the domain class.
To be safe when modifying this use the explicit property name:
interface DATspaceProjection {
String getSUPtitle();
String getSUPid();
}
I created an issue for improvement https://github.com/spring-projects/spring-data-neo4j/issues/2371
I have a controller that accepts a dto object. I need to change the fields that are present in the dto object.
#PatchMapping(value = "/update/{uuid}")
public ResponseEntity<UserDto> update(
#RequestBody UserDto userDto,
#PathVariable("uuid")UUID uuid) throws UserNotFoundException {
User updatedUser = userService.update(
userMapper.userDtoToUser(userDto),
uuid
);
return .....
}
But a userService can only accept entities. I need to use mapper dto -> entity. But the entity cannot have empty fields that come in dto (Let's say that you need to change only one field). What to do in this situation? I know that the controller should not contain logic
Two possible ways to resolve this problem. You have to either change the service method to accept the dto and not the entity or you have to create a #Component class which implements Converter, override the convert method and do the necessary field changes there and then #Autowire GenericConversionService in your controller and call genericConversionService.convert(userDto, User.class);
The converter should look like this:
import org.springframework.core.convert.converter.Converter;
import org.springframework.stereotype.Component;
#Component
public class UserDtoToUser implements Converter<UserDto, User> {
#Override
public User convert(UserDto source) {
User user = new User();
// user.set ..... for all necessary fields
return user;
}
}
EDIT
In case you want to check the validity of the fields you're receiving you can simply use the following annotations to ensure your data is correct: #NotBlank - this checks if a field is null or an empty string, #NotNull - this checks if a field is null, #NotEmpty - this checks if a field is null or an empty collection. Important to remember - you must add the #Valid annotation before the object you want to validate (side note - in case of nested objects you also need to add it to your object fields) so it looks like this #RequestBody #Valid UserDto userDto,
And then for the dto it should look something like this:
import java.util.List;
import javax.validation.constraints.NotBlank;
import javax.validation.constraints.NotEmpty;
import javax.validation.constraints.NotNull;
public class UserDto {
#NotNull
private Integer id;
#NotBlank
private String username;
#NotBlank
private String password;
#NotEmpty
private List<String> roles;
}
Change to fields to whatever is in your dto of course. Also in case you need to do more validations there are a number of other validation annotations you can add.
You could use reflection to check the null properties and BeanUtils for the copying
In Spring that would be the way that I'd to check the empty properties
public static String[] getNullPropertyNames (Object source) {
final BeanWrapper src = new BeanWrapperImpl(source);
PropertyDescriptor[] pds = src.getPropertyDescriptors();
Set<String> emptyNames = new HashSet<>();
for(PropertyDescriptor pd : pds) {
Object srcValue = src.getPropertyValue(pd.getName());
if (srcValue == null) emptyNames.add(pd.getName());
}
return emptyNames.toArray(new String[0]);
}
And then for the copying
User updatedUser = new User();
BeanUtils.copyProperties(userDto, updatedUser, getNullPropertyNames(userDto));
As you say the controller should not contain logic, so for this Spring has an interface, the interface is Validator
There are pros and cons for considering validation as business logic, and Spring offers a design for validation (and data binding) that does not exclude either one of them. Specifically validation should not be tied to the web tier, should be easy to localize and it should be possible to plug in any validator available. Considering the above, Spring has come up with a Validator interface that is both basic ands eminently usable in every layer of an application.
This is what you need to do:
Spring features a Validator interface that you can use to validate objects. The Validator interface works using an Errors object so that while validating, validators can report validation failures to the Errors object.
we have a DTO to which we will validate the fields:
public class Person {
private String name;
private int age;
// the usual getters and setters...
}
To do validations we must implement Validator interface:
public class PersonValidator implements Validator {
/**
* This Validator validates *just* Person instances
*/
public boolean supports(Class clazz) {
return Person.class.equals(clazz);
}
public void validate(Object obj, Errors e) {
if (supports(obj.getClass())) {
ValidationUtils.rejectIfEmpty(e, "name", "name.empty");
Person p = (Person) obj;
if (p.getAge() < 0) {
e.rejectValue("age", "negativevalue");
} else if (p.getAge() > 110) {
e.rejectValue("age", "too.darn.old");
}
}
}
}
If the DTO passes all validations you can map the DTO to an Entity object
You can find more information here Spring Validator
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.
I am trying to incorporate annotated validation rules along with some custom validation. I have a details entity which looks like the following:
public class DetailsEntity {
#NotEmpty(message = "Name is required")
private String name;
private String customField;
public String getName() {
return name;
}
public void setName(String name) {
this.name = name;
}
public String getCustomField() {
return customField;
}
public void setCustomField(String customField) {
this.customField = customField;
}
}
I then have a controller that looks like this:
#Controller
public class EntityController {
#RequestMapping(value = "/create", method = RequestMethod.POST)
public #ResponseBody DetailsEntity create(#RequestBody #Valid
DetailsEntity details) {
//Do some creation work
}
}
This all works great out of the box. The problem is when I try to use a custom validator along with my entity. My validator looks like this:
#Component
public class EntityValidator implements Validator {
#Override
public boolean supports(Class<?> aClass) {
return aClass.isAssignableFrom(DetailsEntity.class);
}
#Override
public void validate(Object o, Errors errors) {
DetailsEntity entity = (DetailsEntity) o;
if (entity.getCustomField().equals("Some bad value")) {
errors.reject("Bad custom value supplied");
}
}
}
I've tried injecting my validator two ways. One is using the #InitBinder in the controller, and the other is setting a global validator in the spring configuration (<mvc:annotation-driven validator="entityValidator" />). Either way I do it, the custom validator works fine, but my #NotEmpty annotation gets ignored. How can I use both the annotations as well as a custom validator?
Use SpringValidatorAdapter as base class of your custom validator and override validate() method:
public void validate(Object target, Errors errors) {
// check JSR-303 Constraints
super.validate(target, errors);
// Add you custom validation here.
}
Or inject a LocalValidationFactoryBean in you custom validator and call to validate(target, errors) before or after your custom validation.
#NotEmpty is a JSR-303 annotation, and we need to use an implementation of it like HiberanteValidator, we need to add Hibernate-Validator jar to your lib directory. Using this jar we can use #NotEmpty, #NotNull...all JSR 303 annotations.