input date error with thymeleaf - spring

I have a html code:
<input type="date" th:field="*{birthday}"/>
When I submit the form I get an error:
Failed to convert property value of type [java.lang.String] to required type [java.util.Calendar] for property birthday; nested exception is java.lang.IllegalStateException: Cannot convert value of type [java.lang.String] to required type [java.util.Calendar] for property birthday: no matching editors or conversion strategy found
I'm using Spring. How can I fix?
Thanks.
The controller:
#RequestMapping(value = "/edit/{id}", method = RequestMethod.POST)
public String updateMember(Model model,
#Valid Member member,
BindingResult bindingResult,
#RequestParam(value="action", required=true) String action) {
System.out.println("updateMember POST - start");
System.out.println(member);
if (bindingResult.hasErrors()) {
System.out.println("bindingResult.hasErrors");
return "member_edit";
}
memberService.update(member);
// todo verificar se precisa dessa linha mesmo chamando o redirect
model.addAttribute("members", memberService.getAll());
return "redirect:/";
}

Spring don't understand the "birthdate" field in java.lang.String because in your Bean it's a java.util.Calendar.
You need to configure the converter. Something like that :
#Configuration
public class ConversionServiceConfig extends WebMvcConfigurerAdapter {
#Bean
public CalendarFormatter calendarFormatter() {
return new CalendarFormatter();
}
#Override
public void addFormatters(FormatterRegistry registry) {
registry.addFormatter(calendarFormatter());
}
}
And your CalendarFormatter looks like this :
public class CalendarFormatter implements Formatter<Calendar> {
final String defaultDateFormat = "dd.MM.yyyy";
#Override
public String print(Calendar object, Locale locale) {
return new SimpleDateFormat(defaultDateFormat).format(object.getTime());
}
#Override
public Calendar parse(String text, Locale locale) throws ParseException {
SimpleDateFormat sdf = new SimpleDateFormat(defaultDateFormat);
Date date = sdf.parse(text);
return sdf.getCalendar();
}
}

Related

Spring controller can accept ZonedDatedTime as #RequestParam but not #RequestBody

The following code works
public #ResponseBody
Map<String, Object> test(#RequestParam #DateTimeFormat(iso = DateTimeFormat.ISO.DATE_TIME) ZonedDateTime startDate,
#RequestParam #DateTimeFormat(iso = DateTimeFormat.ISO.DATE_TIME) ZonedDateTime endDate) {
return null;
}
with this request
https://localhost:8080/api/v1/test?startDate=2000-10-31T01:30:00.000-00:00&endDate=2000-10-31T01:30:00.000-00:00
But the following code throws exception
public #ResponseBody
Map<String, Object> test(#RequestBody #DateTimeFormat(iso = DateTimeFormat.ISO.DATE_TIME) ZonedDateTime startDate,
#RequestBody #DateTimeFormat(iso = DateTimeFormat.ISO.DATE_TIME) ZonedDateTime endDate) {
return null;
}
with this body
{
"endDate":"2000-10-31T01:30:00.000-00:00",
"startDate":"2000-10-31T01:30:00.000-00:00"
}
has this exception
[org.springframework.http.converter.HttpMessageNotReadableException: JSON parse error: Unexpected token (START_OBJECT), expected one of [VALUE_STRING, VALUE_NUMBER_INT, VALUE_NUMBER_FLOAT] for java.time.ZonedDateTime value; nested exception is com.fasterxml.jackson.databind.exc.MismatchedInputException: Unexpected token (START_OBJECT), expected one of [VALUE_STRING, VALUE_NUMBER_INT, VALUE_NUMBER_FLOAT] for java.time.ZonedDateTime value
You must use #ModelAttribute or make object or map for mapping body, because Reflection utils not have opportunity for reading function parameter names (you cant specify property name for mapping).
public class User {
private String name;
private String occupation;
public String getName() {
return name;
}
public void setName(String name) {
this.name = name;
}
}
#RestController
public class MyController {
#ResponseStatus(value = HttpStatus.OK)
#PostMapping(value="/myfoo")
public void process2(#ModelAttribute("email") String email) {
}
#ResponseStatus(value = HttpStatus.OK)
#PostMapping(value="/vals")
public void process(#RequestBody MultiValueMap<String, String> values) {
}
#ResponseStatus(value = HttpStatus.OK)
#PostMapping(value="/user", consumes = MediaType.APPLICATION_JSON_VALUE)
public void process2(#RequestBody User user) {
}
}
ATTENTION
always set name of property for #RequestParam, else you can take error if somebody add not only this parameter or changed function signature

Springboot show error message for invalid date (YearMonth) formats: eg 2020-15

I have a project with Spring Boot and I want to show an error response if the given date format is incorrect.
The correct format is yyyy-MM (java.time.YearMonth) but I want to want to show a message if someone sends 2020-13, 2020-111 or 2020-1.
When I've added a custom validator the debugger goes in there with a valid request but not with an incorrect request. I also tried to use the message.properties with the typeMismatch.project.startdate=Please enter a valid date. but I also don't see that message in my response body.
It seems like the application does not understand my incorrect request and then always throws a BAD REQUEST with empty body, which is not strange because it is not a valid date.
Can someone explain me how I can show an errormessage in the response for these incorrect values?
Or is there no other way then use a String and convert that to the YearMonth object so I can show catch and show an error message?
Request object:
#Getter
#Setter
public class Project {
#NotNull(message = "mandatory")
#DateTimeFormat(pattern = "yyyy-MM")
private YearMonth startdate;
}
Controller:
#RestController
#RequestMapping(produces = MediaType.APPLICATION_JSON_VALUE)
public class ProjectController {
#PostMapping(value = "/project", consumes = MediaType.APPLICATION_JSON_VALUE)
public Project newProject(#Valid #RequestBody Project newProject) {
return projectService.newProject(newProject);
}
}
ExceptionHandler:
#RestControllerAdvice
public class GlobalExceptionHandler extends ResponseEntityExceptionHandler {
#SneakyThrows
#Override
protected ResponseEntity<Object> handleMethodArgumentNotValid(MethodArgumentNotValidException ex, HttpHeaders headers, HttpStatus status, WebRequest request) {
headers.add("Content-Type", "application/json");
ObjectMapper mapper = new ObjectMapper();
Map<String, String> errors = new HashMap<>();
ex.getBindingResult().getAllErrors().forEach(error -> {
String name;
if (error instanceof FieldError)
name = ((FieldError) error).getField();
else
name = error.getObjectName();
String errorMessage = error.getDefaultMessage();
errors.put(name, errorMessage);
});
return new ResponseEntity<>(mapper.writeValueAsString(errors), headers, status);
}
}
Okay, I made a solution which is workable for me.
I've added the solution below for people who find this thread in the future and has the same problem I had.
Create a custom validator with a simple regex pattern:
#Target({ FIELD })
#Retention(RUNTIME)
#Constraint(validatedBy = YearMonthValidator.class)
#Documented
public #interface YearMonthPattern {
String message() default "{YearMonth.invalid}";
Class<?>[] groups() default { };
Class<? extends Payload>[] payload() default { };
}
public class YearMonthValidator implements ConstraintValidator<YearMonthPattern, String> {
#Override
public boolean isValid(String value, ConstraintValidatorContext context) {
Pattern pattern = Pattern.compile("^([0-9]{4})-([0-9]{2})$");
Matcher matcher = pattern.matcher(value);
try {
return matcher.matches();
} catch (Exception e) {
return false;
}
}
}
Update the request object:
#Getter
#Setter
public class Project {
#NotNull(message = "mandatory")
#YearMonthPattern
private String startdate;
public YearMonth toYearMonth(){
return YearMonth.parse(startdate);
}
}
The DateTimeFormat annotation is replaced with our new custom validator and instead of a YearMonth, make it a String. Now the validator annotation can be executed because the mapping to the YearMonth won't fail anymore.
We also add a new method to convert the String startdate to a YearMonth after Spring has validated the request body, so we can use it in the service as a YearMonth instead of having to translate it each time.
Now when we send a requestbody with:
{
"startdate": "2020-1"
}
we get a nice 400 bad request with the following response:
{
"endDate": "{YearMonth.invalid}"
}

Spring REST Service Controller not being validate by #PathVariable and #Valid

#Controller
#EnableWebMvc
#Validated
public class ChildController extends ParentController<InterfaceController> implements InterfaceController{
#Override
#RequestMapping(value = "/map/{name}", produces = "application/json; charset=UTF-8", method = RequestMethod.GET)
#ResponseStatus( HttpStatus.OK)
#ResponseBody
public List<Friends> getAllFriendsByName(
#Valid
#Size(max = 2, min = 1, message = "name should have between 1 and 10 characters")
#PathVariable("name") String name,
#RequestParam(value="pageSize", required=false) String pageSize,
#RequestParam(value="pageNumber", required=false) String pageNumber,
HttpServletRequest request) throws BasicException {
//Some logic over here;
return results;
}
#ExceptionHandler(value = { ConstraintViolationException.class })
#ResponseStatus(value = HttpStatus.BAD_REQUEST)
public String handleResourceNotFoundException(ConstraintViolationException e) {
Set<ConstraintViolation<?>> violations = e.getConstraintViolations();
StringBuilder strBuilder = new StringBuilder();
for (ConstraintViolation<?> violation : violations ) {
strBuilder.append(violation.getMessage() + "\n");
}
return strBuilder.toString();
}
Hi, I am trying to do pretty basic validation for a spring request parameter but it just doesn't seem to call the Exception handler, could someone point me into the right direction
P.S. I keep getting NoHandlerFoundException
Spring doesn't support #PathVariable to be validated using #Valid. However, you can do custom validation in your handler method or if you insist on using #Valid then write a custom editor, convert your path variable value to an object, use JSR 303 bean validation and then use #Valid on that object. That might actually work.
Edit:
Here's a third approach. You can actually trick spring to treat your path variable as a model attribute and then validate it.
1. Write a custom validator for your path variable
2. Construct a #ModelAttribute for your path variable and then use #Validator (yes not #Valid as it doesn't let you specify a validator) on that model attribute.
#Component
public class NameValidator implements Validator {
#Override
public boolean supports(Class<?> clazz) {
return String.class.equals(clazz);
}
#Override
public void validate(Object target, Errors errors) {
String name = (String) target;
if(!StringUtils.isValidName(name)) {
errors.reject("name.invalid.format");
}
}
}
#RequestMapping(value = "/path/{name}", method = RequestMethod.GET)
public List<Friend> getAllFriendsByName(#ModelAttribute("name") #Validated(NameValidator.class) String name) {
// your code
return friends;
}
#ModelAttribute("name")
private String nameAsModelAttribute(#PathVariable String name) {
return name;
}

How to validate Spring MVC #PathVariable values?

For a simple RESTful JSON api implemented in Spring MVC, can I use Bean Validation (JSR-303) to validate the path variables passed into the handler method?
For example:
#RequestMapping(value = "/number/{customerNumber}")
#ResponseBody
public ResponseObject searchByNumber(#PathVariable("customerNumber") String customerNumber) {
...
}
Here, I need to validate the customerNumber variables's length using Bean validation. Is this possible with Spring MVC v3.x.x? If not, what's the best approach for this type of validations?
Thanks.
Spring does not support #javax.validation.Valid on #PathVariable annotated parameters in handler methods. There was an Improvement request, but it is still unresolved.
Your best bet is to just do your custom validation in the handler method body or consider using org.springframework.validation.annotation.Validated as suggested in other answers.
You can use like this:
use org.springframework.validation.annotation.Validated to valid RequestParam or PathVariable.
*
* Variant of JSR-303's {#link javax.validation.Valid}, supporting the
* specification of validation groups. Designed for convenient use with
* Spring's JSR-303 support but not JSR-303 specific.
*
step.1 init ValidationConfig
#Configuration
public class ValidationConfig {
#Bean
public MethodValidationPostProcessor methodValidationPostProcessor() {
MethodValidationPostProcessor processor = new MethodValidationPostProcessor();
return processor;
}
}
step.2 Add #Validated to your controller handler class, Like:
#RequestMapping(value = "poo/foo")
#Validated
public class FooController {
...
}
step.3 Add validators to your handler method:
#RequestMapping(value = "{id}", method = RequestMethod.DELETE)
public ResponseEntity<Foo> delete(
#PathVariable("id") #Size(min = 1) #CustomerValidator int id) throws RestException {
// do something
return new ResponseEntity(HttpStatus.OK);
}
final step. Add exception resolver to your context:
#Component
public class BindExceptionResolver implements HandlerExceptionResolver {
#Override
public ModelAndView resolveException(HttpServletRequest request, HttpServletResponse response, Object handler, Exception ex) {
if (ex.getClass().equals(BindException.class)) {
BindException exception = (BindException) ex;
List<FieldError> fieldErrors = exception.getFieldErrors();
return new ModelAndView(new MappingJackson2JsonView(), buildErrorModel(request, response, fieldErrors));
}
}
}
The solution is simple:
#GetMapping(value = {"/", "/{hash:[a-fA-F0-9]{40}}"})
public String request(#PathVariable(value = "hash", required = false) String historyHash)
{
// Accepted requests: either "/" or "/{40 character long hash}"
}
And yes, PathVariables are ment to be validated, like any user input.
Instead of using #PathVariable, you can take advantage of Spring MVC ability to map path variables into a bean:
#RestController
#RequestMapping("/user")
public class UserController {
#GetMapping("/{id}")
public void get(#Valid GetDto dto) {
// dto.getId() is the path variable
}
}
And the bean contains the actual validation rules:
#Data
public class GetDto {
#Min(1) #Max(99)
private long id;
}
Make sure that your path variables ({id}) correspond to the bean fields (id);
#PathVariable is not meant to be validated in order to send back a readable message to the user. As principle a pathVariable should never be invalid. If a pathVariable is invalid the reason can be:
a bug generated a bad url (an href in jsp for example). No #Valid is
needed and no message is needed, just fix the code;
"the user" is manipulating the url.
Again, no #Valid is needed, no meaningful message to the user should
be given.
In both cases just leave an exception bubble up until it is catched by
the usual Spring ExceptionHandlers in order to generate a nice
error page or a meaningful json response indicating the error. In
order to get this result you can do some validation using custom editors.
Create a CustomerNumber class, possibly as immutable (implementing a CharSequence is not needed but allows you to use it basically as if it were a String)
public class CustomerNumber implements CharSequence {
private String customerNumber;
public CustomerNumber(String customerNumber) {
this.customerNumber = customerNumber;
}
#Override
public String toString() {
return customerNumber == null ? null : customerNumber.toString();
}
#Override
public int length() {
return customerNumber.length();
}
#Override
public char charAt(int index) {
return customerNumber.charAt(index);
}
#Override
public CharSequence subSequence(int start, int end) {
return customerNumber.subSequence(start, end);
}
#Override
public boolean equals(Object obj) {
return customerNumber.equals(obj);
}
#Override
public int hashCode() {
return customerNumber.hashCode();
}
}
Create an editor implementing your validation logic (in this case no whitespaces and fixed length, just as an example)
public class CustomerNumberEditor extends PropertyEditorSupport {
#Override
public void setAsText(String text) throws IllegalArgumentException {
if (StringUtils.hasText(text) && !StringUtils.containsWhitespace(text) && text.length() == YOUR_LENGTH) {
setValue(new CustomerNumber(text));
} else {
throw new IllegalArgumentException();
// you could also subclass and throw IllegalArgumentException
// in order to manage a more detailed error message
}
}
#Override
public String getAsText() {
return ((CustomerNumber) this.getValue()).toString();
}
}
Register the editor in the Controller
#InitBinder
public void initBinder(WebDataBinder binder) {
binder.registerCustomEditor(CustomerNumber.class, new CustomerNumberEditor());
// ... other editors
}
Change the signature of your controller method accepting CustomerNumber instead of String (whatever your ResponseObject is ...)
#RequestMapping(value = "/number/{customerNumber}")
#ResponseBody
public ResponseObject searchByNumber(#PathVariable("customerNumber") CustomerNumber customerNumber) {
...
}
You can create the answer you want by using the fields in the ConstraintViolationException with the following method;
#ExceptionHandler(ConstraintViolationException.class)
protected ResponseEntity<Object> handlePathVariableError(final ConstraintViolationException exception) {
log.error(exception.getMessage(), exception);
final List<SisSubError> subErrors = new ArrayList<>();
exception.getConstraintViolations().forEach(constraintViolation -> subErrors.add(generateSubError(constraintViolation)));
final SisError error = generateErrorWithSubErrors(VALIDATION_ERROR, HttpStatus.BAD_REQUEST, subErrors);
return new ResponseEntity<>(error, HttpStatus.BAD_REQUEST);
}
You need to added an #Validated annotation to Controller class and any validation annotation before path variable field
Path variable may not be linked with any bean in your system. What do you want to annotate with JSR-303 annotations?
To validate path variable you should use this approach Problem validating #PathVariable url on spring 3 mvc
Actually there is a very simple solution to this. Add or override the same controller method with its request mapping not having the placeholder for the path variable and throw ResponseStatusException from it. Code given below
#RequestMapping(value = "/number")
#ResponseBody
public ResponseObject searchByNumber() {
throw new ResponseStatusException(HttpStatus.BAD_REQUEST,"customer number missing")
}

Spring 3 Custom Editor field replacement

Having my ValueObject
UserVO {
long id;
String username;
}
I created custom editor for parsing this object from string id#username
public class UserVOEditor extends PropertyEditorSupport {
#Override
public void setAsText(String text) throws IllegalArgumentException {
Preconditions.checkArgument(text != null,"Null argument supplied when parsing UserVO");
String[] txtArray = text.split("\\#");
Preconditions.checkArgument(txtArray.length == 2, "Error parsing UserVO. Expected: id#username");
long parsedId = Long.valueOf(txtArray[0]);
String username = txtArray[1];
UserVO uvo = new UserVO();
uvo.setUsername(username);
uvo.setId(parsedId);
this.setValue(uvo);
}
#Override
public String getAsText() {
UserVO uvo = (UserVO) getValue();
return uvo.getId()+'#'+uvo.getUsername();
}
in my controller i register
#InitBinder
public void initBinder(ServletRequestDataBinder binder) {
binder.registerCustomEditor(UserVO.class, new UserVOEditor());
}
having in my model object ModelVO
ModelVO {
Set<UserVO> users = new HashSet<UserVO>();
}
after custom editor is invoked all you can see after form submission is
ModelVO {
Set<String> users (linkedHashSet)
}
so when trying to iterate
for(UserVO uvo : myModel.getUser()){ .. }
Im having classCastException .. cannot cast 1234#username (String) to UserVO ..
HOW THIS MAGIC IS POSSIBLE ?
It is not magic, it is because of Generics will be only proved at compile time. So you can put every thing in a Set at runtime, no one will check if you put the correct type in the Set.
What you can try, to make spring a bit more clever, is to put the ModelVO in your command object.
<form:form action="whatEver" method="GET" modelAttribute="modelVO">
#RequestMapping(method = RequestMethod.GET)
public ModelAndView whatEver(#Valid ModelVO modelVO){
...
}

Resources