Spring Boot test entity field validation with custom validation - spring-boot

Setup:
Spring Boot 2.3.3 (spring-boot-starter-validation)
JUnit 5 Jupiter
Lombok
Entity fields can be annotated with simple javax.validation constraints like #Positive.
#Entity
#Data
#Builder
public class Person {
#Id
#GeneratedValue
private long id;
#Column
#Positive(message="not positive")
private int age;
}
This can be tested quite easily:
public class PersonTest {
private Validator validator;
#BeforeEach
public void before() {
validator = Validation.buildDefaultValidatorFactory().getValidator();
}
#Test
public void invalid_becauseNegativeAge() {
Person uut = Person.builder().age(-1).build();
Set<ConstraintViolation<Person>> actual = validator.validate(uut);
assertThat(actual.iterator().next().getMessage()).isEqualTo("not positive");
}
}
Now I add a custom validator to Person:
#Entity
#Data
#Builder
public class Person {
#Id
#GeneratedValue
private long id;
#Column
#Positive(message="not old enough")
private int age;
#ComplexValueConstraint
private String complexValue;
}
Custom annotation:
#Target({ ElementType.FIELD })
#Retention(RetentionPolicy.RUNTIME)
#Constraint(validatedBy = ComplexValueValidator.class)
public #interface ComplexValueConstraint {
String message() default "too simple";
Class<?>[] groups() default {};
Class<? extends Payload>[] payload() default {};
}
Custom validator:
class ComplexValueValidator implements ConstraintValidator<ComplexValueConstraint, Person> {
#Override
public boolean isValid(final Person person, final ConstraintValidatorContext context) {
String complexValue = person.getComplexValue();
if (complexValue.length() < 5) {
return false;
}
return true;
}
}
Now, my test from above fails with a
javax.validation.UnexpectedTypeException: HV000030: No validator could be found for constraint 'ComplexValueConstraint' validating type 'java.lang.String'. Check configuration for 'complexValue'
How can I make this test to work again? I know I could test the validator in isolation, but then my simple annotations like #Positive would remain untested.
Is it because I create the validator in the test class manually? #Autowired won't work somehow.

You are declaring that ComplexValueValidator validates objects of type Person:
ComplexValueValidator implements ConstraintValidator<ComplexValueConstraint, Person>
but the annotation is applied to a field of type String instead of a field of type Person:
#ComplexValueConstraint
private String complexValue;
Hence the message that the constraint can not be applied to a java.lang.String.
Try changing complexValue to be of type Person instead of type String.

Related

Mapstruct - How to convert a DTO String parameter to an Entity object?

I'm new to Mapstruct and I'm trying to understand it properly.
What I want to achieve is converting from a DTO String parameter (carModel) to his Entity, retrieve using Service and Repository.
The problem is that Mapper class generated by Mapstruct is trying to inject the Service class with #Autowired annotation, but it's not working. The service is null.
Here's my #Mapper class:
#Mapper(componentModel = "spring", uses = CarModelService.class)
public interface KitMapper extends EntityMapper<KitDTO, Kit> {
KitMapper INSTANCE = Mappers.getMapper(KitMapper.class);
#Mapping(source = "weight", target = "systemWeight")
#Mapping(source = "carModel", target = "carModel")
Kit toEntity(KitDTO kitDTO);
}
public interface EntityMapper<D, E> {
E toEntity(D dto);
List<E> toEntity(List<D> dtoList);
}
The #Service class:
#Service
#Transactional
public class CarModelService {
private final CarModelRepository carModelRepository;
#Transactional(readOnly = true)
public CarModel findByName(String name) {
return carModelRepository.findByName(name).orElse(null);
}
}
The #Repository class:
#Repository
public interface CarModelRepository extends JpaRepository<CarModel, Long> {
Optional<CarModel> findByName(String carModelName);
}
The DTO and Entity classes:
public class KitDTO {
private String id;
private String carModel; // e.g. "Ferrari Monza"
....
}
#Entity
#Table(name = "kit")
public class Kit implements Serializable {
private static final long serialVersionUID = 1L;
#Id
#GeneratedValue(strategy = GenerationType.SEQUENCE, generator = "sequenceGenerator")
#SequenceGenerator(name = "sequenceGenerator")
#Column(name = "id")
private Long id;
#ManyToOne
private CarModel carModel;
...
}
#Entity
#Table(name = "car_model")
public class CarModel implements Serializable {
private static final long serialVersionUID = 1L;
#Id
#GeneratedValue(strategy = GenerationType.SEQUENCE, generator = "sequenceGenerator")
#SequenceGenerator(name = "sequenceGenerator")
#Column(name = "id")
private Long id;
#Column(name = "name")
private String name;
...
}
The build work properly but the application stop when I try to use the Mapper. It says that carModelService is null.
Here's the mapper generated implementation class:
#Component
public class KitMapperImpl implements KitMapper {
#Autowired // <-- this seems not working
private CarModelService carModelService;
#Override
public Kit toEntity(KitDTO kitDTO) {
if ( kitDTO == null ) {
return null;
}
Kit kit = new Kit();
kit.setSystemWeight( String.valueOf( kitDTO.getWeight() ) );
kit.carModel( carModelService.findByName(kitDTO.getCarModel()) ); // <-- carModelService is null!
// other setters
return kit;
}
}
I've tried many things, using Decorator, #Context, expression, inject the #Mapper class into the #Service class.
I've found many questions but actually no one helped me:
Mapstruct - How can I inject a spring dependency in the Generated Mapper class
#Service Class Not Autowired in org.mapstruct.#Mapper Class
MapStruct mapper not initialized with autowired when debug
Any help would be appreciated! Thanks in advance!
Found the solution!
Instead of calling directly the Mapper method toEntity() from the #RestController class, I injected the mapper in the CarModelService class and created a method that call the mapper.
In this way the flow is:
Controller --> Service --> Mapper
#Service
#Transactional
public class KitService {
private final KitRepository kitRepository;
private final KitSearchRepository kitSearchRepository;
private final KitMapper kitMapper; // <-- mapper declaration
public KitService(KitRepository kitRepository, KitSearchRepository kitSearchRepository, KitMapper kitMapper) {
this.kitRepository = kitRepository;
this.kitSearchRepository = kitSearchRepository;
this.kitMapper = kitMapper; // <-- mapper initilization
}
// here the method which calls mapper
public Kit convertDTOToEntity(KitDTO kitDTO) {
return kitMapper.toEntity(kitDTO);
}
In this way, the generated class by Mapstruct doesn't give error on the CarModelService.
Seems like this approach is the only way to achieve this, create a king of "bridge" between services and mappers.
(You can use also the #Autowired annotation instead of constructor)
Can you please share the error message?
From the information that you shared, I can see the carModel in KitDto is String and in Entity is CarModel class. Not sure how mapstruct's auto generated implementation class implemented this: kit.carModel( carModelService.findByName(kitDTO.getCarModel()) );.
But I would like to share another approach,Don't know this is a best practice or not. In this approach you can create a abstarct class of mapper, in which you can #Autowired repository can manually add those mapping.
I shared the snippet for it. Hopefully this will help you.
#Mapper(componentModel = "spring", nullValuePropertyMappingStrategy = NullValuePropertyMappingStrategy.IGNORE)
public abstract class ProductMapper {
#Autowired
private CarModelService carModelService;
public abstract Kit convertDTOToEntity(KitDTO kitDTO);
public Kit toEntity(KitDTO kitDTO);
{
Kit kit = convertDTOToEntity(kitDTO);
kit.setCarModel(carModelService.findByName(kitDTO.getCarModel()));
return kit;
}
}
Curious about the other approaches, will follow this thread. We can discuss the best practices

Spring Boot request body validation

I am working on Spring REST API and have following controller:
#RestController
#RequestMapping(
value = "/api/Test",
produces = "application/json"
)
public class MyController {
#RequestMapping(method = RequestMethod.POST)
public Response serviceRequest(#Valid #RequestBody ServiceRequest myServiceRequest) {
....
}
}
ServiceRequest has following structure:
#Getter
#Setter
#AllArgsConstructor
#NoArgsConstructor
public class ServiceRequest {
#NotBlank
private LocalDate fromDate;
#NotBlank
private LocalDate toDate;
}
My task is to introduce validation based on combination of fromDate and toDate field's values: if time period between them is longer that 1 week then validation should fail.
What is the best way to archive this please?
You may create a custom constraint validator that will validate the required conditions before processing the request.
DatesDifference.java
#Constraint(validatedBy = DatesDifferenceValidator.class)
#Target(ElementType.TYPE)
#Retention(RetentionPolicy.RUNTIME)
public #interface DatesDifference {
String message() default "Dates difference is more than a week";
Class<?>[] groups() default { };
Class<? extends Payload>[] payload() default { };
}
DatesDifferenceValidator.java
#Component
public class DatesDifferenceValidator implements ConstraintValidator<DatesDifference, ServiceRequest> {
#Override
public boolean isValid(ServiceRequest serviceRequest, ConstraintValidatorContext context) {
System.out.println("validation...");
if (!(serviceRequest instanceof ServiceRequest)) {
throw new IllegalArgumentException("#DatesDifference only applies to ServiceRequest");
}
LocalDate from = serviceRequest.getFromDate();
LocalDate to = serviceRequest.getToDate();
long daysBetween = ChronoUnit.DAYS.between(from, to);
System.out.println("daysBetween "+daysBetween);
return daysBetween <= 7;
}
}
ServiceRequest.java
#Getter
#Setter
#AllArgsConstructor
#NoArgsConstructor
#DatesDifference
public class ServiceRequest {
#NotBlank
private LocalDate fromDate;
#NotBlank
private LocalDate toDate;
}

How to validate inner entities in Spring?

I have a Spring MVC application with javax validation. Is there any way to validate inner entities existence, i.e. many-to-one relationship without marking inner class's id #NotNull and additional long field for FK?
#Entity
#Table
public class MyEntity {
#Id
#GeneratedValue(strategy = GenerationType.IDENTITY)
private Long id;
#NotNull
private String name;
#ManyToOne
#JoinColumn(name = "my_inner_entity_id")
#NotNull
private MyInnerEntity innerEntity;
//#Column(name = "my_inner_entity_id") can't use it
//#NotNull
//private Long innerEntityId;
//setters and getters
}
#Entity
#Table
public class MyInnerEntity {
#Id
#GeneratedValue(strategy = GenerationType.IDENTITY)
//#NotNull can't use it
private Long id;
#NotNull
private Integer value;
#OneToMany(mappedBy = "innerEntity")
private Set<MyEntity> entities = new HashSet<>();
//setters and getters
}
#PostMapping
public MyEntity save(#RequestBody #Valid MyEntity entity) {
//save entity
}
Just add #Valid annotation to innerEntity property:
#Valid
#NotNull
#ManyToOne
private MyInnerEntity innerEntity;
Solved it by writing custom validator:
#Documented
#Constraint(validatedBy = InnerIdValidator.class)
#Target({ElementType.METHOD, ElementType.FIELD})
#Retention(RetentionPolicy.RUNTIME)
public #interface InnerId {
String message() default "entity with id is required";
Class<?>[] groups() default {};
Class<? extends Payload>[] payload() default {};
}
public class InnerIdValidator implements ConstraintValidator<InnerId, IdObject> {
#Override
public boolean isValid(IdObject idObject, ConstraintValidatorContext constraintValidatorContext) {
return idObject != null && idObject.getId() != null;
}
}

Resolving entities with Spring Data Neo4j returns wrong entity types

I'm experiencing some strange behavior when I'm looking up node entities with Spring Data Neo4j (SDN). If I use GraphRepository.findOne(long) it will return an entity with that identifier even though the entity is not of the same type.
This is what my (very) simplified entity structure looks like:
#NodeEntity
protected abstract class BaseEntity {
#GraphId
private Long id;
#JsonIgnore
#RelatedTo(type = RelationType.ENTITY_AUDIT)
private Audit audit;
}
#NodeEntity
public final class Person extends BaseEntity {
#Indexed(indexType = IndexType.FULLTEXT)
private String firstName;
#Indexed(indexType = IndexType.FULLTEXT)
private String lastName;
}
#NodeEntity
public class Audit extends BaseEntity {
#RelatedTo(type = RelationType.ENTITY_AUDIT, direction = Direction.INCOMING)
private BaseEntity parent;
private Long date;
private String user;
}
For every entity type, I've created repositories like this:
#Repository
public interface PersonRepository extends GraphRepository<Person> {}
#Repository
public interface AuditRepository extends GraphRepository<Audit> {}
I've got an abstract base class for my service layer classes. That is what they roughly look like:
public abstract class MyServiceImpl<T extends BaseEntity> implements MyService<T> {
private GraphRepository<T> repository;
public MyServiceImpl(final GraphRepository<T> repository) {
this.repository = repository;
}
#Override
public T read(final Long identifier) throws EntityNotFoundException {
return repository.findOne(identifier);
}
#Override
public T create(final T entity) {
return repository.save(entity);
}
}
#Service
public class PersonServiceImpl extends MyServiceImpl<Person> implements PersonService {
private PersonRepository personRepository;
#Autowired
public PersonServiceImpl(final PersonRepository personRepository) {
super(personRepository);
this.personRepository = personRepository;
}
}
When I execute the following code, the result is not as expected:
Person person = new Person();
person.setFirstName("Test");
person.setLastName("Person");
personService.create(person);
// suppose the person identifier is 1L
final Audit audit = auditRepository.findOne(1L);
You'd expect that the AuditRepository would return null, but this in not the case. Instead, it returns an Audit with identifier 1L and null in all of its properties. It seems that as long as there's a node that corresponds to a given identifier, it will be returned, no mather what its type is. If Person and Audit would have had matching property names, they would contain their values too... Is all this expected behavior, or am I missing something?
For now, I've solved this problem with the code below, where I do the type check myself.
public abstract class MyServiceImpl<T extends BaseEntity> implements MyService<T> {
private GraphRepository<T> repository;
public MyServiceImpl(final GraphRepository<T> repository) {
this.repository = repository;
}
#Override
public T read(final Long identifier) throws EntityNotFoundException {
return get(identifier);
}
protected T get(final Long identifier) throws EntityNotFoundException {
final T entity = repository.findOne(identifier);
final Class<T> type = getServiceType();
if (entity == null || !(type.equals(repository.getStoredJavaType(entity)))) {
throw new EntityNotFoundException(type, identifier);
}
return entity;
}
#SuppressWarnings("unchecked")
private Class<T> getServiceType() {
return (Class<T>) ((ParameterizedType) getClass().getGenericSuperclass())
.getActualTypeArguments()[0];
}
}
If you need more configuration, please let me know.
My framework versions are:
<spring.version>3.2.0.RC1</spring.version>
<neo4j.version>1.8</neo4j.version>
<spring.data.neo4j.version>2.1.0.RELEASE</spring.data.neo4j.version>
we had that behavior before that it failed on the wrong entity type being returned, we changed that behavior so that the type you provide is used to automatically project the node to.
public <S extends PropertyContainer, T> T createEntityFromStoredType(S state, MappingPolicy mappingPolicy) {..}
template. createEntityFromStoredType(node, null) will get you the object with the stored state.
public Class getStoredJavaType(Object entity) {}
gives you the stored class for a node or relationship (or entity)
We had a discussion of changing the behavior back and failing esp. in Repositories.
The question is, what should happen then? An Exception? A Null result? ...
In general if you provide a raw node-id that is valid, returning an error or Null doesn't seem to be like a correct answer either?

Spring Data Neo4J #Indexed(unique = true) not working

I'm new to Neo4J and I have, probably an easy question.
There're NodeEntitys in my application, a property (name) is annotated with #Indexed(unique = true) to achieve the uniqueness like I do in JPA with #Column(unique = true).
My problem is, that when I persist an entity with a name that already exists in my graph, it works fine anyway.
But I expected some kind of exception here...?!
Here' s an overview over basic my code:
#NodeEntity
public abstract class BaseEntity implements Identifiable
{
#GraphId
private Long entityId;
...
}
public class Role extends BaseEntity
{
#Indexed(unique = true)
private String name;
...
}
public interface RoleRepository extends GraphRepository<Role>
{
Role findByName(String name);
}
#Service
public class RoleServiceImpl extends BaseEntityServiceImpl<Role> implements
{
private RoleRepository repository;
#Override
#Transactional
public T save(final T entity) {
return getRepository().save(entity);
}
}
And this is my test:
#Test
public void testNameUniqueIndex() {
final List<Role> roles = Lists.newLinkedList(service.findAll());
final String existingName = roles.get(0).getName();
Role newRole = new Role.Builder(existingName).build();
newRole = service.save(newRole);
}
That's the point where I expect something to go wrong!
How can I ensure the uniqueness of a property, without checking it for myself??
P.S.: I'm using neo4j 1.8.M07, spring-data-neo4j 2.1.0.BUILD-SNAPSHOT and Spring 3.1.2.RELEASE.
I walked into the same trap... as long as you create new entities, you will not see the exception - the last save()-action wins the battle.
Unfortunately, the DataIntegrityViolationException will be raised only in case of update an existing entity!
A detailed description of that behaviour can be found here:
http://static.springsource.org/spring-data/data-graph/snapshot-site/reference/html/#d5e1035
If you are using SDN 3.2.0+ use the failOnDuplicate attribute:
public class Role extends BaseEntity
{
#Indexed(unique = true, failOnDuplicate = true)
private String name;
...
}

Resources