How to solve "Failed to convert property value[...]" - spring

So I have a form with a dropdown in Spring MVC:
<form:form modelAttribute="user" action="registerVerify" method="post">
<!-- Other fields-->
<spring:message code="register.country" />
<form:select path="country" items="${countryList}" />
<br/>
</form:form>
Which is populated by this controller:
#Controller
public class RegisterController {
#RequestMapping(value = "registerForm", method = RequestMethod.GET)
public String register(#ModelAttribute("user") User user, Model model) {
model.addAttribute("user", new User());
model.addAttribute("countryList", cDao.getCountryMap());
model.addAttribute("companyList", cpDao.getCompanyMap());
return "login/registerForm";
}
#RequestMapping(value = "registerVerify", method = RequestMethod.POST)
public String makeRegistration(#ModelAttribute("user") #Valid User user, BindingResult result,
RedirectAttributes redirectAttributes, Model model) {
if (result.hasErrors()) {
System.out.println(result.getFieldError().getDefaultMessage());
model.addAttribute("org.springframework.validation.BindingResult.user", result);
return "redirect:registerForm";
}
if (dao.add(user)) {
redirectAttributes.addFlashAttribute("user", user);
return "redirect:login";
} else {
return "redirect:registerForm";
}
}
// Service classes bellow
I've made some converters
package br.com.sirious.energyquality.converters;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.core.convert.converter.Converter;
import br.com.sirious.energyquality.dao.CompanyDao;
import br.com.sirious.energyquality.models.Company;
public class IdToCompanyConverter implements Converter<String, Company>{
#Autowired
CompanyDao dao;
#Override
public Company convert(String id) {
return dao.getCompanyByID(Integer.parseInt(id));
}
}
And I've set My WebMVCConfig (and WebApplicationInitializer, and spring-context...)
#EnableWebMvc
#Configuration
public class WebConfig extends WebMvcConfigurerAdapter {
#Override
public void addFormatters(FormatterRegistry registry){
registry.addConverter(new IdToCompanyConverter());
}
}
But I still get "Failed to convert property value of type [java.lang.String] to required type [br.com.sirious.energyquality.models.Company] for property 'Company'; nested exception is java.lang.IllegalStateException: Cannot convert value of type [java.lang.String] to required type [br.com.sirious.energyquality.models.Company] for property 'Company': no matching editors or conversion strategy found"
I've checked on many similar posts but none solved the problem. Can someone diagnose what is happening?

Try using the following ConverterRegistry method:
<S,T> void addConverter(Class<S> sourceType,
Class<T> targetType,
Converter<? super S,? extends T> converter)
Which will result in:
public void addFormatters(FormatterRegistry registry){
registry.addConverter(String.class, Company.class, new IdToCompanyConverter());
}

The problem lies on the path of the form:select tag:
<spring:message code="register.country" />
<form:select path="country" items="${countryList}" />
In order to map to the object that is used as reference, the path has to be the id of the object, so path will be: Country.id.
The idea is similar to what is said here: Spring form binding how to do it ? Cannot convert value of type [java.lang.String] to required type
Also, xuesheng has added some interesting information about registering the converter without using web.xml.

Related

In SpringBoot, how do I create a custom validator for a MultipartFile parameter?

I'm using Spring Boot 2.4. I have the following controller with a method that accepts a MultipartFile object.
#RestController
public class MyController extends AbstractController
...
#Override
public ResponseEntity<ResponseData> add(
...
#Parameter(description = "file detail") #Validated #RequestPart("myFile")
MultipartFile myFile,
...
) {
I would like to validate that this MultipartFile contains the data that I want (e.g. is of a particular mime type). So I have written the below validator ...
#Documented
#Constraint(validatedBy = MultipartFileValidator.class)
#Target({ElementType.PARAMETER, ElementType.FIELD, ElementType.METHOD})
#Retention(RetentionPolicy.RUNTIME)
public #interface MultipartFileConstraint {
String message() default "Incorrect file type.";
Class<?>[] groups() default {};
Class<? extends Payload>[] payload() default {};
}
and its implementation class ...
public class MultipartFileValidator
implements ConstraintValidator<MultipartFileConstraint, MultipartFile> {
#Override
public void initialize(final MultipartFileConstraint constraintAnnotation) {
log.info("\n\n\n\nconstructor called\n\n\n\n");
}
#Override
public boolean isValid(
MultipartFile file, ConstraintValidatorContext constraintValidatorContext) {
log.info("Validating file");
...
}
}
However, when I invoke my endpoint, I don't see that my validator is called (for one, the log statement is never printed nor breakpoints hit). What else do I need to do to register my validator for this MultipartFile param?
As per the Spring Documentation:
Can also be used with method level validation, indicating that a
specific class is supposed to be validated at the method level (acting
as a pointcut for the corresponding validation interceptor), but also
optionally specifying the validation groups for method-level
validation in the annotated class. Applying this annotation at the
method level allows for overriding the validation groups for a
specific method but does not serve as a pointcut; a class-level
annotation is nevertheless necessary to trigger method validation for
a specific bean to begin with. Can also be used as a meta-annotation
on a custom stereotype annotation or a custom group-specific validated
annotation.
So, here we have to keep in mind what are the placement of #Validated and validator annotation.
Code:
Controller class : #Validated added at class level and #ValidFile (Custom validator annotation) in the method
#RestController
#Validated
#Slf4j
public class MyController {
#RequestMapping("/add")
public ResponseEntity<ResponseData> add(#ValidFile #RequestParam("file") MultipartFile file) {
log.info("File Validated");
return ResponseEntity.status(HttpStatus.OK).body(new ResponseData("Valid file received"));
}
}
Validator Annotation
#Documented
#Target({ElementType.METHOD, ElementType.FIELD, ElementType.ANNOTATION_TYPE, ElementType.CONSTRUCTOR, ElementType.PARAMETER, ElementType.TYPE_USE})
#Retention(RetentionPolicy.RUNTIME)
#Constraint(validatedBy = {FileValidator.class})
public #interface ValidFile {
Class<? extends Payload> [] payload() default{};
Class<?>[] groups() default {};
String message() default "Only pdf,xml,jpeg,jpg files are allowed";
}
Validator class
#Slf4j
public class FileValidator implements ConstraintValidator<ValidFile, MultipartFile> {
#Override
public void initialize(ValidFile validFile) {
log.info("File validator initialized!!");
}
#Override
public boolean isValid(MultipartFile multipartFile,
ConstraintValidatorContext constraintValidatorContext) {
log.info("Validating file");
String contentType = multipartFile.getContentType();
assert contentType != null;
return isSupportedContentType(contentType);
}
private boolean isSupportedContentType(String contentType) {
return contentType.equals("application/pdf")
|| contentType.equals("text/xml")
|| contentType.equals("image/jpg")
|| contentType.equals("image/jpeg");
}
}
Output :
Success:
{
"message": "Valid file received"
}
Exception handler
#ExceptionHandler(ConstraintViolationException.class)
#ResponseStatus(HttpStatus.BAD_REQUEST)
ResponseEntity<String> handleConstraintViolationException(ConstraintViolationException e) {
return new ResponseEntity<>("Validation error: " + e.getMessage(), HttpStatus.BAD_REQUEST);
}
Failure:
Validation error: Only pdf,xml,jpeg,jpg files are allowed
Below is a small example. I hope it will help.
#Component
public class MultipartFileValidator implements Validator {
#Override
public boolean supports(Class < ? > clazz) {
return MultipartFile.class.isAssignableFrom(clazz);
}
#Override
public void validate(Object target, Errors errors) {
MultipartFile multipartFile = (MultipartFile) target;
if (multipartFile.isEmpty()) {
// Add an error message to the errors list
errors.rejectValue("file", "required.file");
}
}
}

Spring Web MVC validation by Hibernate Validator doesn't draw Errors in BindingResult

I've been using Hibernate Validator in my Spring project. I'm about to validate my JUser Object automatically. i.e, I want Spring to validate the Object and set errors in BindigResult. But It doesn't work.
pom.xml
<properties>
<spring.version>4.3.5.RELEASE</spring.version>
<spring.security.version>4.0.2.RELEASE</spring.security.version>
<hibernate.version>4.3.11.Final</hibernate.version>
<validation-api.version>1.1.0.Final</validation-api.version>
<hibernate-validator.version>5.4.0.Final</hibernate-validator.version>
</properties>
....
applicationContext.xml
...
<tx:annotation-driven transaction-manager="hibernateTransactionManager"/>
<context:annotation-config />
<context:component-scan base-package="my.project.controller" />
<mvc:annotation-driven validator="validator">
<bean id="messageSource" class="org.springframework.context.support.ReloadableResourceBundleMessageSource">
<property name="basename" value="classpath:messages"/>
</bean>
<bean id="validator" class="org.springframework.validation.beanvalidation.LocalValidatorFactoryBean">
<property name="providerClass" value="org.hibernate.validator.HibernateValidator"/>
</bean>
<bean class="org.springframework.validation.beanvalidation.MethodValidationPostProcessor">
<property name="validator" ref="validator"/>
</bean>
<bean id="localeResolver"
class="org.springframework.web.servlet.i18n.CookieLocaleResolver">
<property name="defaultLocale" value="en" />
</bean>
JUser.java
import javax.validation.constraints.NotNull;
import javax.validation.constraints.Size;
import org.hibernate.validator.constraints.NotEmpty;
#Entity
public class JUser implements Officeable {
#Id
private Long id;
#Column(unique = true, nullable = false)
private String username;
private String password;
#NotEmpty
private String firstName;
#NotEmpty
private String lastName;
private String tel;
}
UserController.java
import javax.validation.ConstraintViolationException;
....
#RequestMapping(value = "/update", method = RequestMethod.POST)
public String update2(HttpServletRequest request, Model model, #ModelAttribute("user") #Valid JUser user, BindingResult result) {
if (!result.hasErrors()) {
System.out.println("binding result has no errors for user ");
try {
JUser updated = userService.update(user);
model.addAttribute("user", updated);
} catch (MessageException | DataIntegrityViolationException ex) {
result.reject("user", ex.getMessage());
} catch (ConstraintViolationException cvex) {
for (ConstraintViolation cv : cvex.getConstraintViolations()) {
result.rejectValue(cv.getPropertyPath().toString(),cv.getMessageTemplate() , cv.getMessage());
}
}
}
return "user/manage";
}
As you see in the above controller method I want Spring to validate the user Object and set errors in BindigResult. But It does not work.
For example when user has empty firstName I face the output:
output:
binding result has no errors for user
and I have to catch hibernate thrown exceptions by hand:
ConstraintViolationException: may not be empty ...
more description. I've used String #Validated annotation and It did not work as well. I've read more than ten related stackoverflow questions and they didn't solved my problem.
First thing, can you test if validate is working after adding below code?
pom.xml
<dependency>
<groupId>org.hibernate</groupId>
<artifactId>hibernate-validator</artifactId>
<version>5.2.4.Final</version>
</dependency>
#Bean // in configuration
public Validator validator() {
ValidatorFactory validatorFactory = Validation.buildDefaultValidatorFactory();
return validatorFactory.getValidator();
}
#Autowired //in controller
private Validator validator;
public <T> void validate(T t) {
Set validate = this.validator.validate(t);
if(!validate.isEmpty()) {
throw new RuntimeException();
}
}
If this works, then can suggest you further to simplify it.
As per spring-mvc-4.3.xsd
The bean name of the Validator that is to be used to validate
Controller model objects. This attribute is not required, and only
needs to be specified if a custom Validator needs to be configured. If
not specified, JSR-303 validation will be installed if a JSR-303
provider is present on the classpath.
I don't see you wrote any custom validator so you can change
<mvc:annotation-driven validator="validator">
to support the default JSR-303
<mvc:annotation-driven />
Example: Spring 3 MVC and JSR303 #Valid example
Update 1
Could you also try removing validation-api.version
This transitively pulls in the dependency to the Bean Validation API
(javax.validation:validation-api:1.1.0.Final).
You can use the ExceptionHandler approach. Just add this method in your controller class. I haven't tested this with the #ModelAttribute although it should work, I know for sure that it works with #RequestBody.
#ExceptionHandler(MethodArgumentNotValidException.class)
#ResponseStatus(HttpStatus.BAD_REQUEST)
#ResponseBody
public ErrorDTO processValidationError(MethodArgumentNotValidException ex) {
BindingResult result = ex.getBindingResult();
List<FieldError> fieldErrors = result.getFieldErrors();
// your own custom error dto class
ErrorDTO errorDto = constructErrors(fieldErrors);
return errorDto;
}
If you are using HibernateValidator you must tell to use the HibernateValidator class
By looking the LocalValidatorFactoryBean javadoc
When talking to an instance of this bean through the Spring or JSR-303 Validator interfaces, you'll be talking to the default Validator of the underlying ValidatorFactory. This is very convenient in that you don't have to perform yet another call on the factory, assuming that you will almost always use the default Validator anyway. This can also be injected directly into any target dependency of type Validator!
So you should use the setProviderClass method in order to specify what class to use
Here it's what I did (i'm using annotation based config but it's the same):
WebMvcConfig
#Override
public Validator getValidator() {
LocalValidatorFactoryBean lvfb = new LocalValidatorFactoryBean();
lvfb.setProviderClass(HibernateValidator.class);
return lvfb;
}
Model:
#Entity
#Table(name = "CANDIDATO")
public class Candidato extends AbstractModel {
private static final long serialVersionUID = -5648780121365553697L;
.
.
.
private String corsoLaurea;
.
.
.
#Column(name="CORSO_LAUREA", nullable=true)
#NotEmpty
public String getCorsoLaurea() {
return corsoLaurea;
}
}
controller method
#RequestMapping(method = { RequestMethod.PUT }, value = { "/salvaModificheCandidato" })
public ResponseEntity<BaseResponse<String>> modificaCandidato(#RequestBody #Valid ModificaCandidatoDto dto, BindingResult bindResult) throws Exception
{
BaseResponse<String> result = null;
HttpStatus status = null;
try
{
this.candidatoSvc.modificaCandidato(dto);
result = new BaseResponse<String>();
status = HttpStatus.OK;
result.setDescrizioneOperazione("Aggiornamento candidato terminato correttamente");
result.setEsitoOperazione(status.value());
result.setPayload(Collections.EMPTY_LIST);
}
catch (Exception e)
{
result = new BaseResponse<String>();
status = HttpStatus.INTERNAL_SERVER_ERROR;
String message = "Errore nella modifica del candicato con ID "+dto.getIdCandidato()+"; "+e.getMessage();
logger.error(message, e);
result.setDescrizioneOperazione(message);
result.setEsitoOperazione(status.value());
}
return new ResponseEntity<BaseResponse<String>>(result, status);
}
With this configuration I find in bindinresult errors for both the DTO and the Model
I hope this can be useful
EDITED PART
I saw that your issue is to have the bindingresult not empty when you try to persist your object; I changed my code in this way
No change to the model (I used the hibernate validation NotEmpty annotation)
I changed my service method in this way:
#Override
#Transactional(transactionManager = "hibTx", rollbackFor = CandidatiDbException.class, readOnly = false)
public void modificaCandidato(ModificaCandidatoDto dto, BindingResult brErrors) throws CandidatiDbException {
try
{
dao.modificaCandidato(dto, brErrors);
} catch (Exception e)
{
String message = "Errore nella modifica del candidato con ID "+dto.getIdCandidato()+"; "+e.getMessage();
logger.error(message, e);
throw new CandidatiDbException(message);
}
}
As you can see I passed the BindingResult object to the method
Then I changed my DAO impl in this way:
public class CandidatoDaoImpl<T> implements ICandidatoDao<T> {
#Autowired
#Qualifier("candValidator")
Validator validator;
public void modificaCandidato(ModificaCandidatoDto dto, BindingResult brErrors) {
Session sessione = getSession();
sessione.setCacheMode(CacheMode.IGNORE);
Candidato candidato = sessione.load(Candidato.class, dto.getIdCandidato());
.
.
.
validator.validate(candidato, brErrors);
if( !brErrors.hasErrors() )
{
sessione.saveOrUpdate(candidato);
}
}
}
Finally I updated my WebMvcConfig in this way:
#Configuration
#EnableWebMvc
#Import(SharedSpringConfig.class)
#PropertySource( value={"classpath:configuration.properties"}, encoding="UTF-8", ignoreResourceNotFound=false)
public class WebMvcConfig extends WebMvcConfigurerAdapter {
#Bean(name="candValidator")
public Validator validator()
{
LocalValidatorFactoryBean lvfb = new LocalValidatorFactoryBean();
lvfb.setProviderClass(HibernateValidator.class);
return lvfb;
}
#Override
public Validator getValidator() {
return validator();
}
}
In this way when I have some error on the object I want to persist I have the BindingResult object not empty and no exception is raised
I hope this can be useful
Angelo

Create own class that transforms HTTP request to object in Spring?

I would like to create own class that will transform HTTP request and initializes object from this HTTP request in my Spring MVC application. I can create object by defining parameters in method but I need to do mapping in my own way and do it manually.
How can I do it with my own implementation that will pass to Spring and it will use it seamlessly?
Update1
Solution that kindly provided Bohuslav Burghardt doesn't work:
HTTP Status 500 - Request processing failed; nested exception is
java.lang.IllegalStateException: An Errors/BindingResult argument is
expected to be declared immediately after the model attribute, the
#RequestBody or the #RequestPart arguments to which they apply: public
java.lang.String
cz.deriva.derivis.api.oauth2.provider.controllers.OAuthController.authorize(api.oauth2.provider.domain.AuthorizationRequest,org.springframework.ui.Model,org.springframework.validation.BindingResult,javax.servlet.http.HttpServletRequest,javax.servlet.http.HttpServletResponse)
Maybe I should mention that I use own validator:
public class RequestValidator {
public boolean supports(Class clazz) {
return AuthorizationRequest.class.equals(clazz);
}
public void validate(Object obj, Errors e) {
AuthorizationRequest request = (AuthorizationRequest) obj;
if ("foobar".equals(request.getClientId())) {
e.reject("clientId", "nomatch");
}
}
}
and declaration of my method in controller (please not there is needed a validation - #Valid):
#RequestMapping(value = "/authorize", method = {RequestMethod.GET, RequestMethod.POST})
public String authorize(
#Valid AuthorizationRequest authorizationRequest,
BindingResult result
) {
}
I have two configurations classes in my application.
#Configuration
#EnableAutoConfiguration
#EnableWebMvc
#PropertySource("classpath:/jdbc.properties")
public class ApplicationConfig {
}
and
#Configuration
#EnableWebMvc
public class WebappConfig extends WebMvcConfigurerAdapter {
#Override
public void addArgumentResolvers(List<HandlerMethodArgumentResolver> argumentResolvers) {
argumentResolvers.add(new AuthorizationRequestArgumentResolver());
}
}
What is wrong?
Update 2
The problem is with param BindingResult result, when I remove it it works. But I need the result to process it when some errors occur.
If I understand your requirements correctly, you could implement custom HandlerMethodArgumentResolver for that purpose. See example below for implementation details:
Model object
public class AuthorizationRequestHolder {
#Valid
private AuthorizationRequest authorizationRequest;
private BindingResult bindingResult;
// Constructors, accessors omitted
}
Resolver
public class AuthorizationRequestMethodArgumentResolver implements HandlerMethodArgumentResolver {
#Override
public boolean supportsParameter(MethodParameter parameter) {
return AuthorizationRequestHolder.class.isAssignableFrom(parameter.getParameterType());
}
#Override
public Object resolveArgument(MethodParameter parameter,
ModelAndViewContainer mavContainer,
NativeWebRequest webRequest,
WebDataBinderFactory binderFactory) throws Exception {
HttpServletRequest request = (HttpServletRequest) webRequest.getNativeRequest();
// Map the authorization request
AuthorizationRequest authRequest = mapFromServletRequest(request);
AuthorizationRequestHolder authRequestHolder = new AuthorizationRequestHolder(authRequest);
// Validate the request
if (parameter.hasParameterAnnotation(Valid.class)) {
WebDataBinder binder = binderFactory.createBinder(webRequest, authRequestHolder, parameter.getParameterName());
binder.validate();
authRequestHolder.setBindingResult(binder.getBindingResult());
}
return authRequestHolder;
}
}
Configuration
#Configuration
#EnableWebMvc
public class WebappConfig extends WebMvcConfigurerAdapter {
#Override
public void addArgumentResolvers(List<HandlerMethodArgumentResolver> argumentResolvers) {
argumentResolvers.add(new AuthorizationRequestMethodArgumentResolver());
}
}
Usage
#RequestMapping("/auth")
public void doSomething(#Valid AuthRequestHolder authRequestHolder) {
if (authRequestHolder.getBindingResult().hasErrors()) {
// Process errors
}
AuthorizationRequest authRequest = authRequestHolder.getAuthRequest();
// Do something with the authorization request
}
Edit: Updated answer with workaround to non-supported usage of #Valid with HandlerMethodArgumentResolver parameters.

Invalid target for Validator in spring error?

Hi all I am getting the following error whenever I am trying to invoke validator in my spring
Servlet.service() for servlet spring threw exception: java.lang.IllegalStateException: Invalid target for Validator
Please have a look and help me out in this error, previously I user the validation for login page and it is working fine but now its not working.
Here is my code snippet .
Controller
#Controller
public class NewUserRegistration
{
#Autowired
private UserService userService;
#Autowired
private NewUserValidator newUserValidator;
#InitBinder
public void initBinder(WebDataBinder binder)
{
binder.setValidator(newUserValidator);
}
#RequestMapping(value="/newUserAdd", method=RequestMethod.POST)
public String addUser(#ModelAttribute("user")#Valid User user,BindingResult result, Model model)
{
return "NewUser";
}
}
Validator
#Component
public class NewUserValidator implements Validator
{
#Override
public boolean supports(Class<?> classz)
{
return NewUserRegistration.class.equals(classz);
}
#Override
public void validate(Object obj, Errors error)
{
//Validation login for fields
}
}
JSP Page
<form:form action="newUserAdd" method="POST" modelAttribute="user">
<center>
<table>
<tr><td>User Id:</td><td><input name="userId" type="text" /></td><td><font color="red"><c:out value="${userIdError}" /></font> </td></tr>
<tr><td>Password:</td><td><input name="userPassword" type="password"/></td><td><font color="red"><c:out value="${userPasswordError}" /></font></td></tr>
<tr><td>Confirm Password:</td><td><input name="userConfirmPassword" type="password"/></td><td><font color="red"><c:out value="${userPasswordError}" /></font></td></tr>
<tr><td>Name:</td><td><input name="userName" type="text"/></td><td><font color="red"><c:out value="${userPasswordError}" /></font></td></tr>
<tr><td></td><td><input type="submit" value="Create"/></td></tr>
</table>
</center>
</form:form>
The problem is actually in Validator class you are using NewUserRegistration's object which is wrong because you want to validate your User's object not your NewUserRegistration's object.
#Override
public boolean supports(Class<?> classz)
{
return NewUserRegistration.class.equals(classz);
}
which should be
#Override
public boolean supports(Class<?> classz)
{
return User.class.equals(classz);
}

Spring mvc url parameter

I cannot display may restaurant.
I.ve got my controller class:
#Controller
public class RestaurantController extends MultiActionController{
private RestaurantDAO restaurantDAO;
public void setRestaurantDAO(RestaurantDAO restaurantDAO) {
this.restaurantDAO = restaurantDAO;
}
#RequestMapping("/restaurant/{restaurantId}")
public ModelAndView restaurantid(#PathVariable("contactId") int id,
HttpServletRequest request,
HttpServletResponse response) throws Exception {
Restaurant restaurant = restaurantDAO.findRestaurantById(id);
ModelMap modelMap = new ModelMap();
modelMap.addAttribute("restaurant", restaurant);
return new ModelAndView("restaurant", modelMap);
}
}
im my jsp just:
<c:out value="${restaurant.name }"
in my spring-servlet.xml:
<bean name="/restaurant/**" class="web.RestaurantController" >
<property name="restaurantDAO" ref="myRestaurantDAO"/>
</bean>
Because you mixed up restaurantId and contactId
#RequestMapping("/restaurant/{restaurantId}")
public ModelAndView restaurantid(#PathVariable("contactId") ...
I guess when you change #PathVariable("contactId") to #PathVariable("restaurantId") it will work.
And add #RequestMapping("/restaurant/**") to your controller:
#RequestMapping("/restaurant/**")
#Controller
public class RestaurantController extends MultiActionController{
BTW: What is a MultiActionController?

Resources