I have simple controller method which receives file:
#ResponseBody
public MyDto createProduct(MyDto dto, #RequestParam(value = "file") MultipartFile file) {
}
The problem is that Spring doesn't throw exception if user didn't chose file in form. But I need to be sure that user chose some file. I tried to add required = true but it didn't help (moreover is by default set to true)
Actually Spring throws exception only if my form doesn't contain parameter named file at all:
Required MultipartFile parameter 'file' is not present
But if parameter present and file is not chose in HTML form then there is no exception.
How to solve this problem?
Thanks
You can push your MultipartFile file as a property of your DTO and write a custom validation annotation e.g. #FilePresent. Your signature would than be something like
public MyDto createProduct(#Validated MyDto dto, BindingResult result)
your would annotate your file property inside MyDto
#FilePresent
private MultipartFile file;
Your custom validation code would be something like:
The #FilePresent annotation
#Documented
#Retention(RUNTIME)
#Constraint(validatedBy = {FilePresentMultipartFileValidator.class})
#Target({ METHOD, FIELD, ANNOTATION_TYPE, CONSTRUCTOR, PARAMETER })
public #interface FilePresent {
String message() default "{your.package.FilePresent.message}";
Class<?>[] groups() default {};
Class<? extends Payload>[] payload() default {};
String value() default "true";
}
Custom Validator
public class FilePresentMultipartFileValidator implements ConstraintValidator<FilePresent, MultipartFile> {
#Override
public void initialize(FilePresent constraintAnnotation) {
//NOOP
}
#Override
public boolean isValid(MultipartFile value, ConstraintValidatorContext context) {
return !(value == null || value.isEmpty());
}
}
the final move is to have a ValidationMessages.properties file on the classpath (and/or its localized equivalents) having the key your.package.FilePresent.message with the message value you choose
Related
I have a Controller where I have the path veriable resourceId as type UUID like shown below.
#GetMapping(value = "{resourceId}")
public ResponseEntity<MyClass> findOneByResourceId(#PathVariable("resourceId") UUID resourceId) {
return new ResponseEntity<>(myService.findOneByResourceId(resourceId), HttpStatus.OK);
}
Everything works fine exception when Jackson is trying to deserialize an invalid UUID if such as "9e3b414a" an exception is thrown. Failed to convert value of type 'java.lang.String' to required type 'java.util.UUID'; nested exception is java.lang.IllegalArgumentException: Invalid UUID string: 9e3b414a
I have already a custom UuidDeserializer class (shown below) which I am using in other areas of my code successfully. I'd like to use this deserializer as well on the pathVariable.
public class UuidDeserializer extends JsonDeserializer<UUID> {
#Override
public UUID deserialize(JsonParser jsonParser, DeserializationContext deserializationContext) throws IOException {
var stringToValidate = jsonParser.getValueAsString().trim();
if (MyUtils.isStringInvalidUUID(stringToValidate)) {
throw new MyCustomException("Invalid UUID value")
}
return UUID.fromString(stringToValidate);
}
I cannot seem to get Spring to use this custom deserializer though on a path variable. I tried putting the #JsonDeserialize on the path variable but it doesn't work. HELP!
#GetMapping(value = "{resourceId}")
public ResponseEntity<MyClass> findOneByResourceId(#PathVariable("resourceId")
#JsonDeserialize(using = UuidDeserializer.class) UUID resourceId) {
return new ResponseEntity<>(myService.findOneByResourceId(resourceId), HttpStatus.OK);
}
You can create a custom validation annotation to check UUID, for example #UUIDValidation so first create our custom annotation.
#Target({ FIELD, METHOD, PARAMETER, ANNOTATION_TYPE })
#Retention(RUNTIME)
#Constraint(validatedBy = UUIDValidator.class)
#Documented
public #interface UUIDValidation {
String message() default "Mandatory fields missing";
Class<?>[] groups() default {};
Class<? extends Payload>[] payload() default {};
}
Also its custom validator, let's call UUIDValidator that should implement ConstraintValidator and override the isValid method.
public class UUIDValidator implements ConstraintValidator<UUIDValidation, UUID> {
#Override
public boolean isValid(UUID value, ConstraintValidatorContext context) {
if (MyUtils.isStringInvalidUUID(value)) {
throw new MyCustomException("Invalid UUID value");
}
try {
UUID.fromString(value);
return Boolean.TRUE;
} catch (IllegalArgumentException ex) {
return Boolean.FALSE;
}
}
}
And we can call our custom validation annotation on the #PathVariable
#GetMapping(value = "{resourceId}")
public ResponseEntity<MyClass> findOneByResourceId(#PathVariable("resourceId")
#UUIDValidation UUID resourceId) {
return new ResponseEntity<>(myService.findOneByResourceId(resourceId), HttpStatus.OK);
}
I am trying to throw exception while uploading file if the file format is not csv type. Here is my implementation (some parts are omitted for clarity):
ValidFile:
#Documented
#Target({METHOD, FIELD, ANNOTATION_TYPE, CONSTRUCTOR, PARAMETER, TYPE_USE})
#Retention(RUNTIME)
#Constraint(validatedBy = {FileValidator.class})
public #interface ValidFile {
//...
}
FileValidator:
public class FileValidator implements ConstraintValidator<ValidFile, MultipartFile> {
#Override
public boolean isValid(MultipartFile multipartFile,
ConstraintValidatorContext context) {
// ...
}
}
ValidationErrorResponse:
public class ValidationErrorResponse {
// have "fieldName" and "response" data
}
ErrorHandlingControllerAdvice:
#ControllerAdvice
class ErrorHandlingControllerAdvice {
//I tried to use something like that >>>
#ExceptionHandler(NotValidFormatException.class)
#ResponseStatus(HttpStatus.UNPROCESSABLE_ENTITY)
#ResponseBody
ValidationErrorResponse onNotValidFormatException(NotValidFormatException e) {
//...
}
}
And, as there is not a file field in db, I validated uploaded file parameter as shown below (I want to validate on Controller):
Controller:
public ResponseEntity<ResponseMessage> uploadFile(
#Valid #ValidFile #RequestParam("file") MultipartFile file)
throws Exception {
employeeService.create(file);
return ResponseEntity.ok(new ResponseMessage("File uploaded.));
}
I also tried to use #Validated, #RequestPart("file") as parameter, but does not make any sense.
When I try to upload an invalid type (pdf) and trace the exception, I see "Failed to parse csv file: (line 322) invalid char between encapsulated token and delimiter" error. So, how can I catch the exception in my onNotValidFormatException method?
Given multiple REST resources to gather order information.
/order/{id}
/order/{id}/positions
/order/{id}/invoice
/order/{id}/shipment
Within the a Srping Boot 2 application it's implemented across multiple controllers, e.g. OrderController, InvoiceController, etc.
Currently every controller uses the OrderRepository to ensure the the order with the given id exists. Otherwise it throws an exception. It's always the same replicated code.
#RestController
public class OrderController {
// ...
#GetMapping("order/{id}")
public Order getCustomer(#PathVariable final Integer id) {
return this.orderRepository.findById(id)
.orElseThrow(() -> new IllegalArgumentException("order not found"));
}
}
Does the framework provide a callback to write the order id check just once?
I found the AntPathMatcher but it seems not the right way, since it provides just an boolean interface.
This is usually a good case for bean validation. While there is already builtin support for many cases of validation (#Size, #NotNull, ...), you can also write your own custom constraints.
First of all, you need to define an annotation to validate your ID, for example:
#Documented
#Constraint(validatedBy = OrderIdValidator.class)
#Target({PARAMETER})
#Retention(RUNTIME)
public #interface ValidOrderId {
String message() default "Invalid order ID";
Class<?>[] groups() default {};
Class<? extends Payload>[] payload() default {};
}
Since your order ID is always a parameter for your controller mappings, you could use ElementType.PARAMETER to only allow the annotation for method parameters.
The next step is to add the #Constraint annotation to it and point to a custom validator class (eg. OrderIdValidator):
#Component
public class OrderIdValidator implements ConstraintValidator<ValidOrderId, Integer> {
private OrderRepository repository;
public OrderIdValidator(OrderRepository repository) {
this.repository = repository;
}
#Override
public boolean isValid(Integer id, ConstraintValidatorContext constraintValidatorContext) {
return repository.existsById(id);
}
}
By implementing the isValid method, you can check whether or not the order exists. If it doesn't exist, an exception will be thrown and the message() property of the #ValidOrderId annotation will be used as a message.
The last step is to add the #Validated annotation to all of your controllers, and to add the #ValidOrderId annotation to all order ID parameters, for example:
#Validated // Add this
#RestController
public class OrderController {
#GetMapping("order/{id}")
public Order getCustomer(#PathVariable #ValidOrderId final Integer id) { // Add #ValidOrderId
// Do stuff
}
}
If you prefer to use a different response status for your validations, you could always add a class annotated with the #ControllerAdvice annotation and use the following method:
#ExceptionHandler(ConstraintViolationException.class)
public void handleConstraints(HttpServletResponse response) throws IOException {
response.sendError(HttpStatus.BAD_REQUEST.value());
}
I want to validate the #RequestBody of an endpoint in my Spring #RestController. So I created a method like this:
#RequestMapping(value = ...)
public ResponseEntity<...> myPostMethod(#RequestBody MyBean myBean) throws Exception {
MyBean is decorated with a custom #Constraint and its respective validation logic is implemented on a ConstraintValidator class that I created. This validator has a method like:
#Override
public boolean isValid(MyBean value, ConstraintValidatorContext context) {
That's where all the validation logic takes place. When it fails, isValid returns false and I can use that context param to build a validation error message the way I want. In addition, myPostMethod also fails with an automatic (because I do not throw it myself) MethodArgumentNotValidException that I'm going to capture on a global handler in order to render a generic ResponseEntity. It all works as expected. The question is: how do I customize not only the validation error message, but also the whole ConstraintViolationException? I want to provide more data (from my business domain) inside the exception to render in the response body json.
I found the answer:
https://docs.jboss.org/hibernate/validator/5.4/api/org/hibernate/validator/constraintvalidation/HibernateConstraintValidatorContext.html#withDynamicPayload-java.lang.Object-
public boolean isValid(MyBean value, ConstraintValidatorContext constraintValidatorContext) {
HibernateConstraintValidatorContext context = constraintValidatorContext.unwrap(HibernateConstraintValidatorContext.class);
// (...)
context.buildConstraintViolationWithTemplate( "{foo}" )
.withDynamicPayload(anyAdditionalInfo)
.addConstraintViolation();
return false;
}
Let's assume that the additional parameter you wanted to pass is called myParam.
First, declare an accessor for that parameter in your Constraint interface;
public #interface MyConstraint {
String message();
Class<?>[] groups() default {};
Class<? extends Payload>[] payload() default {};
String myParam() default "some.value";
}
Then in the ConstraintValidator, you could access these params like:
public class MyValidator implements ConstraintValidator<MyConstraint, String> {
private String myParam;
#Override
public void initialize(OpcoConstraint parameters) {
code = parameters.myParam();
}
#Override
public boolean isValid(String value, ConstraintValidatorContext context) {
HibernateConstraintValidatorContext hibernateContext = context.unwrap(
HibernateConstraintValidatorContext.class
);
// you can pass any object as the payload
hibernateContext.withDynamicPayload(Map.of("myParam", myParam));
}
}
If you catch the ConstraintViolationException from an exception handler and want to access the parameter from the Exception itself:
To retrieve ConstraintViolation (s) out of ConstraintViolationException use:
constraintViolationException.getConstraintViolations();
Then to retrieve the dynamic payload out of a ConstraintViolation:
(Map) ((ConstraintViolationImpl) constraintViolation).getDynamicPayload(Map.class))
There is a form for which the phone number is sometimes required and sometimes it is not and the form is validated by validator class that spring calls. I would like that validator class to know if when the field is required and when it is ok if it's empty, but I am not sure about the correct approach to implement this. The controller and validation process looks like this:
#RequestMapping(value = "my/url", method = RequestMethod.POST)
public ModelAndView someMethod(#Valid T myForm, BindingResult bindingResult) {
//Calling the validation...
if (bindingResult.hasErrors()) {
// didn't pass validation
} else {
// is ok!
}
}
The form looks like this. The idea isI could add another field that would determine if phone number is required or not, if I would find a way to use it later when validating phone field. It has the #Phone annotation:
public class MyForm implements Serializable {
private static final long serialVersionUID = 1L;
#Phone
private String phone;
public String getPhone() {
return phone;
}
public void setPhone(String phone) {
this.phone = phone;
}
}
The #Phone annotation specifies a class that will validate the data
#Target({CONSTRUCTOR, FIELD, METHOD, PARAMETER})
#Retention(RUNTIME)
#Documented
#Constraint(validatedBy = ValidateField.class)
public #interface Phone {
String message() default "{default.phone.errormsg}";
int maxLength() default 64;
Class<?>[] groups() default {};
Class<? extends Payload>[] payload() default {};
}
Here is where the problem starts. The class that validates data looks like this and the thing is that I would like to be able to check if the phone is required this time or not in this form, but I don't see any way to pass other form fields in this class other than the phone number value and I can't access form object here anyway:
public class PhoneValidator implements ConstraintValidator<Phone, String> {
//...
#Override
public boolean isValid(String value, ConstraintValidatorContext context) {
// I want to check if the "required" field was passed from form here
// to know if it's ok for the field to be empty
if (StringUtils.isEmpty(value) /* && isNotRequired */) {
return true;
}
return notBlank(value, context)
&& maxLength(value, context, maxLength) /* and other constraints... */;
}
//...
}
I am new Spring validation stuff and I am not even sure if I am going in the right direction, I would be thank full if someone could guide me to a proper way of solving this issue.