Create a validation method which throw MethodArgumentNotValidException - spring

I'm trying to validate this class :
public class RegisterRequest {
#Email(message = "format e-mail non valide") #NotNull(message = "ne doit pas être vide")
private String email;
#NotEmpty(message = "ne doit pas être vide")
#NotBlank(message = "ne doit pas être vide")
#NotNull(message = "ne doit pas être vide")
private String prenom;
#NotEmpty(message = "ne doit pas être vide")
#NotBlank(message = "ne doit pas être vide")
#NotNull(message = "ne doit pas être vide")
private String nom;
#Length(min = 13,max = 13 ,message = "doit contenir exactement 13 chiffres")
#NotNull(message = "ne doit pas être vide")
#Pattern(regexp = "^\\d*$", message = "ne doit contenir que des chiffres")
#Pattern(regexp = "^[12]\\w*$", message = "doit commencer par 1 ou 2")
private String cin;
#NotNull(message = "ne doit pas être vide")
#Pattern(regexp = "^(?!.* .*)[\\d+() ]{9,20}$", message = "ne doit contenir que des chiffres , des parentheses et plus")
#Length(min = 9,max = 20)
private String telephone;
#NotNull(message = "ne doit pas être vide")
#Length(min = 5,max = 20,message = "la taille de caractère doit être entre 5 et 20")
#Pattern(regexp = "^(?=[a-zA-Z\\d._-]*$)(?!.*[_.]{2})[^_.].*[^_.]$",message = "ne doit contenir de caractère special a part _ , .,-")
private String login;
}
after construction with ObjectMapper like this :
#PostMapping("/register")
public ResponseEntity<?> register(
#RequestParam("file") MultipartFile file,
#RequestParam Map<String,Object> req
) throws UserNotFoundException, EntreeException, RoleNotFoundException, IOException {
ObjectMapper mapper = new ObjectMapper();
RegisterRequest registerRequest = mapper.convertValue(req,RegisterRequest.class);
return controllerResponse(registerRequest,"");
}
as you see i can't use the #Valid annotation here (note : if there is a method to use the annotation directly i'll prefere it. but i didnt find any way to do it.
so i've found that it is better to create a validator class so after some research i've found this method
#Slf4j
public class CustomValidator {
ValidatorFactory factory = Validation.buildDefaultValidatorFactory();
Validator validator = factory.getValidator();
public Boolean validate(Object object){
Set<ConstraintViolation<Object>> violations = validator.validate(object);
log.info(violations.toString());
String errorMessage =
violations.stream().map(ConstraintViolation::getMessage).collect(Collectors.joining(", "));
throw new MethodArgumentNotValidException(errorMessage);
}
}
but it is not working take two argument in his constructor :
MethodParameter parameter, BindingResult bindingResult
so my question is how create this argument and how to throw it like #Valid throw it or is there any other way to handle the validation error

So, i've found this solution but i'm not throwing MethodArgumentNotValidException anymore
i've created my own exception class like this :
#Getter #Setter
public class ArgumentValidationExption extends Exception{
private final Map<String, String> errors;
public ArgumentValidationExption(Map<String,String> errors) {
super();
this.errors = errors;
}
#ExceptionHandler(ArgumentValidationExption.class)
public ResponseEntity<?> ArgumentInvalid(ArgumentValidationExption arg){
return jsonResponse(false,arg.getErrors(),400,arg.getClass().getSimpleName());
}
and in my validator class ill just throw my exception like this :
public void validate(Object object) throws ArgumentValidationExption {
Set<ConstraintViolation<Object>> violations = validator.validate(object);
if (violations.size() == 0 ) return ;
Map<String, String> errors = new HashMap<>();
for (ConstraintViolation<Object> constraint: violations
) {
errors.put(constraint.getPropertyPath().toString(), constraint.getMessageTemplate());
}
throw new ArgumentValidationExption(errors);
}
}
if you found a better solution please let me know

Related

Mockito how to test save method with DTO

I dont know how can i test save method with mockito. The problem is that the test is incorrect because I am creating a new object in the service and I have no idea how to fix it
Service
#Validated
public class ProductService {
private final CategoryService categoryService;
public ProductService(CategoryService categoryService){
this.categoryService = categoryService;
}
public void addProduct(Long categoryId,#Valid AddProductDto addProductDto) throws DataAccessException{
CategoryModel categoryModel = categoryService.getCategoryById(categoryId);
ProductModel productModel = new ProductModel();
productModel.setProducent(addProductDto.getProducent());
productModel.setPrice(addProductDto.getPrice());
productModel.setName(addProductDto.getName());
productModel.setSlider(addProductDto.getSlider());
productModel.setImage(addProductDto.getImage());
productModel.setDescription(addProductDto.getDescription());
productModel.setQuantityAvailable(addProductDto.getQuantityAvailable());
productModel.setCategoryModel(categoryModel);
productRepository.save(productModel);
}
}
DTO:
#Data
#Builder
public class AddProductDto implements Serializable {
#NotEmpty(message = "Wprowadź poprawną nazwę nazwe")
#Length(min = 3, max = 220, message = "Wprowadź poprawną długość nazwy przedmiotu")
private final String name;
#NotNull(message = "Uzupełnij Slider")
private final Boolean slider;
#NotNull(message = "Wprowadź poprawną ilość")
#Min(message = "Wprowadź poprawną ilość", value = 0)
private Integer quantityAvailable;
#Length(min = 1, max = 220, message = "Wprowadź poprawną długość nazwy producenta")
#NotEmpty(message = "Wprowadź poprawnego producenta")
private final String producent;
#Length(min = 5, max = 220, message = "Wprowadź poprawny opis przedmiotu")
#NotEmpty(message = "Wprowadź poprawny opis")
private final String description;
#NotEmpty(message = "Wprowadź poprawne zdjęcie")
#Length(min = 1, max = 240, message = "Wprowadź poprawne zdjęcie przedmiotu")
private final String image;
#Min(message = "Wprowadź poprawną cenę", value = 1)
#NotNull(message = "Wprowadź cenę")
private final Double price;
}
My test:
#Test
void testAddProductSuccess(){
//given
AddProductDto addProductDto = AddProductDto.builder()
.producent("Logitech")
.price(1.0)
.name("Logitech G-403")
.slider(false)
.image("myszka1.jpg")
.description("Dzięki przemyślanej budowie waży jedynie 59 g, dzięki czemu Twoja dłoń nie męczy się podczas użytkowania.")
.quantityAvailable(100)
.build();
ProductModel productModel = ProductModel.builder()
.producent("Logitech")
.price(1.0)
.name("Logitech G-403")
.slider(false)
.image("myszka1.jpg")
.description("Dzięki przemyślanej budowie waży jedynie 59 g, dzięki czemu Twoja dłoń nie męczy się podczas użytkowania.")
.quantityAvailable(100)
.build();
Set<ConstraintViolation<AddProductDto>> violations = validator.validate(addProductDto);
//when
productService.addProduct(1L,addProductDto);
//verify
verify(productRepository,times(1)).save(productModel);
assertTrue(violations.isEmpty());
}
Test Result
Argument(s) are different! Wanted:
productRepository.save(
model.ProductModel#55a88417
);
Actual invocations have different arguments:
productRepository.save(
ProductModel#18acfe88
);
The test is incorrect because in the service I create a new ProductModel object and it is not the same object that I am checking. And I have no idea how to fix it.
Best way to test JpaRepository is using the #DataJpaTest annotation on you test class. Inject repository under test and that's it. You should forget the hell of creating when and then statements. You can find the reference guide here

How to annotate request body to describe examples

I'm struggling with describe the requestBody correctly.
I have this Dto as Request body:
public #Data class ContactDto {
#Parameter(description = "Mailadress required if messageType is MAIL")
private String mailAddress;
#Parameter(description = "Phonenumber required if messageType is not MAIL", example =
"0041791234567")
private String phoneNumber;
#Parameter(description = "Message type which will be used to inform the user", examples = {
#ExampleObject(name = "SMS", value = "SMS"),
#ExampleObject(name = "MAIL", value = "MAIL")
})
private MessageType messageType;
}
And this in the Controller:
#PostMapping(consumes = "application/json")
public ResponseEntity<Object> createWichtel(#RequestBody() final WichtelDetailsDto wichtelDetailsDto)
{
return new ResponseEntity<>(HttpStatus.CREATED);
}
I'm using Spring with springdoc-openapi-ui
But when I'm opening the swagger-ui, the description above does not show.
What is the error here?
Just use #ApiParam
public #Data class ContactDto {
#ApiParam(value = "Mailadress required if messageType is MAIL")
private String mailAddress;
#ApiParam(value = "Phonenumber required if messageType is not MAIL", example =
"0041791234567")
private String phoneNumber;
#ApiParam(value = "Message type which will be used to inform the user", example = "{(name = \"SMS\", value = \"SMS\")}")
private MessageType messageType;

Persistence in hibernate and spring

I have a question about Spring and Hibernate and that is, if for example I want to persist two entities at the same time, such as Customer and Address, that when a customer is registered, their address is automatically saved as well.
It is that the address is retrieved from an address api external to my application and I want that information to be saved in the Address entity when the client registers.
My entities are the following:
Customer :
public class Customer{
#Id
#GeneratedValue(strategy = GenerationType.IDENTITY)
private Long id;
#NotBlank(message = "no puede estar vació")
private String nombre;
#NotBlank(message = "no puede estar vació")
#Column(name = "apellido_paterno")
private String apellidoPaterno;
#NotBlank(message = "no puede estar vació")
#Column(name = "apellido_materno")
private String apellidoMaterno;
#NotBlank(message = "no puede estar vació")
private String telefono;
#ManyToOne(fetch = FetchType.LAZY)
#JoinColumn(name = "address_id")
#JsonIgnoreProperties({"hibernateLazyInitializer","handler"})
private Address address;
}
Address:
public class Address{
#Id
#GeneratedValue(strategy = GenerationType.IDENTITY)
private Long id;
#NotBlank(message = "no puede estar vació")
private String calle;
#NotBlank(message = "no puede estar vació")
private String colonia;
#NotNull(message = "no puede estar vació")
#Column(name = "no_exterior")
private Integer noExterior;
#Column(name = "no_interior")
private Integer noInterior;
#NotBlank(message = "no puede estar vació")
private String municipio;
#NotNull(message = "no puede estar vació")
private Integer cp;
#NotBlank(message = "no puede estar vació")
private String estado;
#OneToMany(mappedBy = "adress")
private List<Customer> customer;
}
Since I have it, the address must already be created so that it can be associated with the client, however, I want that when the client enters their data, including the address, they are created at the same time and the association is maintained.
When you persist the Entity "Customer" you should create a object Customer and a object Address, and set to the Customer the Address object and add to the Customer list in Address object, the object Customer. I mean, you should do it in your business logic layer, or your service.

No property found SpringBoot JPA

I encouter this error while running the app, i dont understand why.. i found information on the web, but can't understand what's wrong. Thanks for help.
2 ) Another question regarding this, should i put :
List<HistoriqueDeploiement> findByIdNamespaceAndIdService(Namespace id_namespace, Service id_service);
Or
List<HistoriqueDeploiement> findByIdNamespaceAndIdService(Integer id_namespace, Integer id_service);
The error :
Error creating bean with name 'checkConfigDeploiementRepository': FactoryBean threw exception on object creation; nested exception is java.lang.IllegalArgumentException: Failed to create query for method public abstract java.util.List com.example.jpa.repository.CheckConfigDeploiementRepository.findByIdNamespaceAndIdService(com.example.jpa.model.Namespace,com.example.jpa.model.Service)! No property namespace found for type Integer! Traversed path: CheckConfigDeploiement.id.
The Entity :
#Entity
#Table(name = "historiquedeploiement")
#Data
#EqualsAndHashCode(callSuper=false)
#NoArgsConstructor
#AllArgsConstructor
public class HistoriqueDeploiement extends AuditModel {
#Id
#GeneratedValue(strategy = GenerationType.IDENTITY)
#Column(name="id", nullable=false, unique=true)
private Integer id;
#ManyToOne(fetch = FetchType.LAZY, optional = false)
#JoinColumn(name = "id_namespace", nullable = false)
#JsonIdentityInfo(generator=ObjectIdGenerators.PropertyGenerator.class, property="id")
#JsonIdentityReference(alwaysAsId=true)
#JsonProperty("id_namespace")
private Namespace namespace;
#ManyToOne(fetch = FetchType.LAZY, optional = false)
#JoinColumn(name = "id_service", nullable = false)
#JsonIdentityInfo(generator=ObjectIdGenerators.PropertyGenerator.class, property="id")
#JsonIdentityReference(alwaysAsId=true)
#JsonProperty("id_service")
private Service service;
#NotEmpty(message = "Le GitCommit ne peut être vide")
#Size(max = 255)
private String gitCommit;
#NotEmpty(message = "Le TagVersion ne peut être vide")
#Size(max = 100)
private String tagVersion;
#NotEmpty(message = "Le Actionby ne peut être vide")
#Size(max = 255)
private String actionBy;
}
NamespaceEntity ( same with service..)
#Entity
#Table(name = "namespace")
#Data
#EqualsAndHashCode(callSuper=false)
#NoArgsConstructor
#AllArgsConstructor
public class Namespace extends AuditModel {
#Id
#GeneratedValue(strategy = GenerationType.IDENTITY)
#Column(name="id", nullable=false, unique=true)
private Integer id;
#NotEmpty
#Size(max = 100)
#Column(unique = true)
private String namespace;
#OneToMany(mappedBy = "namespace", cascade = CascadeType.ALL, orphanRemoval = true)
private List<HistoriqueDeploiement> historiquedeploiements = new ArrayList<>();
public void addHistoriqueDeploiement(HistoriqueDeploiement historiquedeploiement) {
historiquedeploiements.add(historiquedeploiement);
historiquedeploiement.setNamespace(this);
}
public void removeHistoriqueDeploiement(HistoriqueDeploiement historiquedeploiement) {
historiquedeploiements.remove(historiquedeploiement);
historiquedeploiement.setNamespace(null);
}
}
The repo, i don't understand what i'm doing wrong :
...
#Repository
public interface HistoriqueDeploiementRepository extends JpaRepository<HistoriqueDeploiement, Integer> {
List<HistoriqueDeploiement> findAll();
List<HistoriqueDeploiement> findByIdNamespace(Integer id);
List<HistoriqueDeploiement> findByIdNamespaceAndIdService(Namespace id_namespace, Service id_service);
List<HistoriqueDeploiement> findByIdNamespaceAndLogCreatedAtBetween(Namespace id_namespace, Date datedebut, Date datefin);
List<HistoriqueDeploiement> findByIdNamespaceAndLogCreatedAt(Namespace id_namespace, Date date);
}
Okay so I looked at your problem and here is what I found. The types you assigned to your repository interface method parameters are wrong.
You are looking to obtain a list of HistoriqueDeploiement entities whose Namespace and Service entities have specific IDs. Note that IDs of Namespace and Service entities are Integer types. So in order to solve you can simply rewrite your methods as follows:
#Repository
public interface HistoriqueDeploiementRepository extends
JpaRepository<HistoriqueDeploiement, Integer> {
List<HistoriqueDeploiement> findAll();
List<HistoriqueDeploiement> findByNamespaceId(Integer id);
List<HistoriqueDeploiement> findByNamespaceIdAndServiceId(Integer id_namespace, Integer id_service);
List<HistoriqueDeploiement> findByNamespaceIdAndLogCreatedAtBetween(Integer id_namespace, Date datedebut, Date datefin);
List<HistoriqueDeploiement> findByNamespaceIdAndLogCreatedAt(Integer id_namespace, Date date);
}
Note that major change here is that we replaced Namespace and Service types with Integer type, which is the actual type of their IDs

Different validation JSON responses in Spring Data REST

I'm using Spring Boot, Spring Data REST, Hibernate, JPA.
I'm experiencing a strange issue, probably due to a wrong configuration.
I'm posting relevant parts of my configuration:
#Configuration
public class RestConfig extends RepositoryRestConfigurerAdapter {
#Autowired
private Validator validator;
public static final DateTimeFormatter ISO_FIXED_FORMAT = DateTimeFormatter.ofPattern("yyyy-MM-dd'T'HH:mm:ss'Z'")
.withZone(ZoneId.of("Z"));
#Bean
public RootResourceProcessor rootResourceProcessor() {
return new RootResourceProcessor();
}
#Override
public void configureExceptionHandlerExceptionResolver(ExceptionHandlerExceptionResolver exceptionResolver) {
}
#Override
public void configureValidatingRepositoryEventListener(ValidatingRepositoryEventListener validatingListener) {
validatingListener.addValidator("beforeCreate", validator);
validatingListener.addValidator("beforeSave", validator);
super.configureValidatingRepositoryEventListener(validatingListener);
}
}
Configuration of validator an message source:
#Configuration
#EnableTransactionManagement
#EnableJpaAuditing(auditorAwareRef = "springSecurityAuditorAware")
public class CustomConfiguration {
#Bean
public MessageSource messageSource() {
ReloadableResourceBundleMessageSource messageSource = new ReloadableResourceBundleMessageSource();
messageSource.setBasenames("classpath:/i18n/messages");
// messageSource.setDefaultEncoding("UTF-8");
// set to true only for debugging
messageSource.setUseCodeAsDefaultMessage(false);
messageSource.setCacheSeconds((int) TimeUnit.HOURS.toSeconds(1));
messageSource.setFallbackToSystemLocale(false);
return messageSource;
}
/**
* Enable Spring bean validation
* https://docs.spring.io/spring/docs/current/spring-framework-reference/core.html#validation
*
* #return
*/
#Bean
public LocalValidatorFactoryBean validator() {
LocalValidatorFactoryBean factoryBean = new LocalValidatorFactoryBean();
factoryBean.setValidationMessageSource(messageSource());
return factoryBean;
}
#Bean
public MethodValidationPostProcessor methodValidationPostProcessor() {
MethodValidationPostProcessor methodValidationPostProcessor = new MethodValidationPostProcessor();
methodValidationPostProcessor.setValidator(validator());
return methodValidationPostProcessor;
}
my bean:
#Entity
#EntityListeners(MovementListener.class)
public class Movement extends AbstractEntity {
private static final long serialVersionUID = -5073555669170105151L;
#NotBlank
#Column(nullable = false)
private String description;
#NotNull
#ManyToOne(fetch = FetchType.LAZY, optional = false)
private PaymentType paymentType;
#NotNull
#Column(nullable = false)
#Enumerated(EnumType.STRING)
private Direction direction;
#Min(value = 0)
#NotNull
#Column(nullable = false)
private BigDecimal amount = BigDecimal.ZERO;
#NotNull
#Column(nullable = false)
private Instant currencyDate = Instant.now();
#JsonProperty(access = Access.READ_ONLY)
#ApiModelProperty(readOnly = true)
#NotNull(message = "{NotNull.movement.agent}")
#ManyToOne(fetch = FetchType.LAZY, optional = false)
protected User agent;
#JsonProperty(access = Access.READ_ONLY)
#ApiModelProperty(readOnly = true)
#NotNull(message = "{NotNull.movement.checkpoint}")
#ManyToOne(fetch = FetchType.LAZY, optional = false)
protected CheckPoint checkPoint;
#ManyToOne(fetch = FetchType.LAZY, optional = true)
private Customer customer;
#Type(type = "json")
#Column(columnDefinition = "json")
private String details;
and related listener:
#Component
public class MovementListener {
private Logger log = LogManager.getLogger();
public static WorkSessionRepository workSessionRepository;
#Autowired
public void init(WorkSessionRepository workSessionRepository) {
MovementListener.workSessionRepository = workSessionRepository;
}
#PrePersist
private void onSaveOrUpdate(Movement value) {
try {
if (value != null && value.isNew()) {
WorkSession workSession = workSessionRepository.findByAgentUsernameAndEndDateIsNull();
if (workSession != null) {
value.agent = workSession.getAgent();
value.checkPoint = workSession.getCheckPoint();
}
}
} catch (Exception e) {
log.error("Error into MovementListener during #PrePersist.", e);
}
}
}
I'm exposing the persist method of this bean via Spring data REST:
#Transactional
#PreAuthorize("isAuthenticated()")
public interface MovementRepository extends PagingAndSortingRepository<Movement, Long> {
}
If I enable the validator both in beforeCreate and beforeSave, when I POST my entities with errors I see a well formatted ConstraintViolationException JSON error.
Wrong request:
curl -X POST --header 'Content-Type: application/json' --header 'Accept: application/hal+json' -d '{ \
"amount": 0, \
"currencyDate": "2017-10-10T20:49:57.959Z", \
"description": "string", \
"details": "string", \
"direction": "IN", \
"paymentType": "", \
"version": 0 \
}' 'http:/
then I've a well formatted reply:
{
"errors": [
{
"entity": "Movement",
"property": "checkPoint",
"invalidValue": null,
"message": "Ogni movimento contabile deve essere associato ad un checkpoint. Ripetere lautenticazione e ripetere loperazione."
},
{
"entity": "Movement",
"property": "paymentType",
"invalidValue": null,
"message": "Il campo non può essere vuoto. Inserire un valore valido e ripetere loperazione."
},
{
"entity": "Movement",
"property": "agent",
"invalidValue": null,
"message": "Ogni movimento contabile deve essere associato ad un operatore. Ripetere lautenticazione e ripetere loperazione."
}
]
}
But, because two fields of the bean are set inside the listener I created, I can't leave enabled the beforeCreate event and I should just leave only beforeSave.
If I do this, with the same request, I've this reply:
{
"timestamp": "2017-10-10T21:04:39.450+0000",
"status": 500,
"error": "Internal Server Error",
"exception": "javax.validation.ConstraintViolationException",
"message": "Validation failed for classes [it.rebus.server.model.accounting.Movement] during persist time for groups [javax.validation.groups.Default, ]\nList of constraint violations:[\n\tConstraintViolationImpl{interpolatedMessage='may not be null', propertyPath=paymentType, rootBeanClass=class it.rebus.server.model.accounting.Movement, messageTemplate='{javax.validation.constraints.NotNull.message}'}\n]",
"path": "/api/v1/movements"
}
Seems Spring Data REST is not managing correclty the ConstraintViolationException but I don't understand why. I would need a hint to pick up the right way to solve the problem.

Resources