Spring MockMvc Passing Nested Form Parameters - spring

I have the following form
public class MyForm {
private Account account;
}
public class Account {
private String firstName;
}
How do I pass firstName parameter?
(The following approach does not work)
mockMvc.perform(post("/xyz")
.param("account.firstName", "John"))
.andExpect(model().hasErrors())
.andExpect(view().name("/xyz"))
.andExpect(status().isOk())

Finally I resolved this issue. Since I am using standalone setup I had to define validator and messagesource.
void setupTest() {
MockitoAnnotations.initMocks(this)
this.mockMvc = MockMvcBuilders.standaloneSetup(getController())
.setValidator(getValidator())
.alwaysDo(MockMvcResultHandlers.print())
.build()
}
private MessageSource getMessageSource() {
ResourceBundleMessageSource messageSource = new ResourceBundleMessageSource();
messageSource.setBasename("messages");
messageSource.setUseCodeAsDefaultMessage(true);
return messageSource;
}
private LocalValidatorFactoryBean getValidator() {
def validator = new LocalValidatorFactoryBean()
validator.setValidationMessageSource(getMessageSource());
return validator;
}

Related

Why I'm getting org.springframework.context.NoSuchMessageException in spring boot?

I'm having properties/messages file name:
messages_en.properties
And I have the property inside:
country.AF.name=Afghanistan
here is the messages class:
#ApplicationScope
#Configuration
#Slf4j
public class Messages {
private ResourceBundleMessageSource messageSource;
private MessageSourceAccessor accessor;
#PostConstruct
private void init() {
messageSource();
accessor = new MessageSourceAccessor(messageSource, Locale.ENGLISH);
log.info("Messages initialized");
}
public String get(String code) {
return accessor.getMessage(code);
}
public void messageSource() {
messageSource = new ResourceBundleMessageSource();
messageSource.setBasenames("classpath:messages_en.properties");
messageSource.setUseCodeAsDefaultMessage(true);
}
The full error I'm getting is as follow:
at com.fasterxml.jackson.databind.ser.std.BeanSerializerBase.serializeFields(BeanSerializerBase.java:770) ~[jackson-databind-2.12.3.jar:2.12.3]
Postman show wrong results, I need to get all the countries:
Did you add the below things message Sources bean configuration
messageSource
.setBasename("classpath:messages_en.properties")

Spring boot properties that depend on other properties

Do we have anyway where we can define one property is dependent on the other property?
messages.properties
product.name=XYZ
product.title=XYZ title
I have already tried the below one but its not working
product.name=XYZ
product.title=${product.name} title
#Bean
public MessageSource messageSource() {
ReloadableResourceBundleMessageSource messageSource =
new ReloadableResourceBundleMessageSource();
messageSource.setBasename("classpath:messages");
messageSource.setDefaultEncoding("UTF-8");
return messageSource;
}
#Component
public class Messages {
#Autowired
private MessageSource messageSource;
private MessageSourceAccessor accessor;
#PostConstruct
private void init() {
accessor = new MessageSourceAccessor(messageSource);
}
public String get(String code, #Nullable Object[] args) {
return accessor.getMessage(code, args);
}
public String get(String code) {
return accessor.getMessage(code);
}
}

Not able to get error object in JSON format while using #Valid and MessageSource to get display errors in Spring boot

I am currently learning Spring REST and I am trying to build a demo spring boot app. Incase of DTO object has validation error I want to show it as below:
{
"errors": [
{
"code": "first_error_code",
"message": "1st error message"
"field":"field_name"
}
]
}
Where the code in above JSON should display the validation message that I have given in my entity class i.e
#NotEmpty(message = "{name.not.empty}")
String name;
then code should be name.not.empty and message should be taken from messages.properties file.
Now to achieve this, I used several tutorials. Below are the classes:
Main class: (Included MessageSource and LocalValidatorFactoryBean)
#SpringBootApplication
#EnableSwagger2
public class Demo3PathvariableApplication implements WebMvcConfigurer {
#Bean
public MessageSource messageSource() {
ReloadableResourceBundleMessageSource messageSource = new ReloadableResourceBundleMessageSource();
messageSource.setBasename("classpath:messages");
messageSource.setDefaultEncoding("UTF-8");
return messageSource;
}
#Bean
public LocalValidatorFactoryBean validator(MessageSource messageSource) {
LocalValidatorFactoryBean bean = new LocalValidatorFactoryBean();
bean.setValidationMessageSource(messageSource());
return bean;
}
public static void main(String[] args) {
SpringApplication.run(Demo3PathvariableApplication.class, args);
}
/*
* To enable matrix variables, configurePathMatch() method of WebMvcConfigurer
* needs to overriden. Matrix variables are disabled by default and the
* following configuration
*
* urlPathHelper.setRemoveSemicolonContent(false);
*
* should be present in the overriden method to enable the same. see below
* method.
*/
#Override
public void configurePathMatch(PathMatchConfigurer configurer) {
UrlPathHelper urlPathHelper = new UrlPathHelper();
urlPathHelper.setRemoveSemicolonContent(false);
configurer.setUrlPathHelper(urlPathHelper);
}
#Override
public void addCorsMappings(CorsRegistry registry) {
registry.addMapping("/**").allowedMethods("GET", "POST");
}
/* For Swagger Document Generation */
#Bean
public Docket api() {
return new Docket(DocumentationType.SWAGGER_2).select()
.apis(RequestHandlerSelectors.basePackage("com.infytel.controller")).paths(PathSelectors.any()).build()
.useDefaultResponseMessages(false);
// To scan for RestControllers from this package and For disabling default
// response messages
}
}
Controller class:
#RestController
#RequestMapping("/customers")
#Api(value = "CustomerController, REST APIs that deal with Customer DTO")
public class CustomerController {
#Autowired
private CustomerService customerService;
#PostMapping(consumes = "application/json")
public ResponseEntity createCustomer(#RequestBody #Valid CustomerDTO customer, Errors errors) {
return ResponseEntity.ok(customerService.createCustomer(customer));
}
}
FieldErrorDTO.java:
public class FieldErrorDTO {
private String errorCode;
private String message;
private String field;
public FieldErrorDTO(String errorCode, String message, String field) {
this.errorCode = errorCode;
this.message = message;
this.field = field;
}
//Getter setter
ValidationErrorDTO.java:
public class ValidationErrorDTO {
private List<FieldErrorDTO> fieldErrors = new ArrayList<>();
public ValidationErrorDTO() {
super();
}
public void addFieldError(String errorCode, String message, String field) {
FieldErrorDTO error = new FieldErrorDTO(errorCode, message, field);
fieldErrors.add(error);
}
public List<FieldErrorDTO> getFieldErrors() {
return fieldErrors;
}
public void setFieldErrors(List<FieldErrorDTO> fieldErrors) {
this.fieldErrors = fieldErrors;
}
}
RestErrorHandler .java
#ControllerAdvice
public class RestErrorHandler {
#Autowired
private MessageSource messageSource;
#ResponseStatus(BAD_REQUEST)
#ResponseBody
#ExceptionHandler(MethodArgumentNotValidException.class)
public ValidationErrorDTO processValidationError(MethodArgumentNotValidException ex) {
BindingResult result = ex.getBindingResult();
List<org.springframework.validation.FieldError> fieldErrors = result.getFieldErrors();
return processFieldErrors(fieldErrors);
}
private ValidationErrorDTO processFieldErrors(List<FieldError> fieldErrors) {
ValidationErrorDTO dto = new ValidationErrorDTO();
for (FieldError fieldError : fieldErrors) {
String localizedErrorMessage = resolveLocalizedErrorMessage(fieldError);
dto.addFieldError(fieldError.getCode(), localizedErrorMessage, fieldError.getField());
}
return dto;
}
private String resolveLocalizedErrorMessage(FieldError fieldError) {
Locale currentLocale = LocaleContextHolder.getLocale();
String localizedErrorMessage = messageSource.getMessage(fieldError, currentLocale);
return localizedErrorMessage;
}
}
messages.properties
name.not.empty=Please provide a name.
email.not.valid=Please provide valid email id.
age.adult.only=Age should be more than 18.
Now with all these config, I am able to see below JSON,
{
"fieldErrors": [
{
"errorCode": "NotEmpty",
"message": "Please provide a name.",
"field": "name"
},
{
"errorCode": "Email",
"message": "Please provide valid email id.",
"field": "email"
}
]
}
How do I acheive this requirement, where instead of "errorCode": "NotEmpty", I want show
"errorCode": "name.not.empty"
From CustomerDTO class?
To do so you need to change you processFieldErrors:
First remove "{}" from your anotations:
#NotEmpty(message = "name.not.empty")
String name;
Second:
private ValidationErrorDTO processFieldErrors(List<FieldError> fieldErrors) {
ValidationErrorDTO dto = new ValidationErrorDTO();
for (FieldError fieldError : fieldErrors) {
String localizedErrorMessage = resolveLocalizedErrorMessage(fieldError);
dto.addFieldError(fieldError.getDefaultMessage(), localizedErrorMessage, fieldError.getField());
}
return dto;
}
And third, change your message.getMessage:
private String resolveLocalizedErrorMessage(FieldError fieldError) {
Locale currentLocale = LocaleContextHolder.getLocale();
String localizedErrorMessage = messageSource.getMessage(fieldError.getDefaultMessage(), null, currentLocale);
return localizedErrorMessage;
}
This way you would retrieve the key for the message. In your example it will be :
name.not.empty
Hope this helps

Inject Repository inside ConstraintValidator with Spring 4 and message interpolation configuration

I created a small example project to show two problems I'm experiencing in the configuration of Spring Boot validation and its integration with Hibernate.
I already tried other replies I found about the topic but unfortunately they didn't work for me or that asked to disable Hibernate validation.
I want use a custom Validator implementing ConstraintValidator<ValidUser, User> and inject in it my UserRepository.
At the same time I want to keep the default behaviour of Hibernate that checks for validation errors during update/persist.
I write here for completeness main sections of the app.
Custom configuration
In this class I set a custom validator with a custom MessageSource, so Spring will read messages from the file resources/messages.properties
#Configuration
public class CustomConfiguration {
#Bean
public MessageSource messageSource() {
ReloadableResourceBundleMessageSource messageSource = new ReloadableResourceBundleMessageSource();
messageSource.setBasenames("classpath:/messages");
messageSource.setUseCodeAsDefaultMessage(false);
messageSource.setCacheSeconds((int) TimeUnit.HOURS.toSeconds(1));
messageSource.setFallbackToSystemLocale(false);
return messageSource;
}
#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;
}
}
The bean
Nothing special here if not the custom validator #ValidUser
#ValidUser
#Entity
public class User extends AbstractPersistable<Long> {
private static final long serialVersionUID = 1119004705847418599L;
#NotBlank
#Column(nullable = false)
private String name;
/** CONTACT INFORMATION **/
#Pattern(regexp = "^\\+{1}[1-9]\\d{1,14}$")
private String landlinePhone;
#Pattern(regexp = "^\\+{1}[1-9]\\d{1,14}$")
private String mobilePhone;
#NotBlank
#Column(nullable = false, unique = true)
private String username;
#Email
private String email;
#JsonIgnore
private String password;
#Min(value = 0)
private BigDecimal cashFund = BigDecimal.ZERO;
public User() {
}
public String getName() {
return name;
}
public void setName(String name) {
this.name = name;
}
public String getLandlinePhone() {
return landlinePhone;
}
public void setLandlinePhone(String landlinePhone) {
this.landlinePhone = landlinePhone;
}
public String getMobilePhone() {
return mobilePhone;
}
public void setMobilePhone(String mobilePhone) {
this.mobilePhone = mobilePhone;
}
public String getUsername() {
return username;
}
public void setUsername(String username) {
this.username = username;
}
public String getEmail() {
return email;
}
public void setEmail(String email) {
this.email = email;
}
public String getPassword() {
return password;
}
public void setPassword(String password) {
this.password = password;
}
public BigDecimal getCashFund() {
return cashFund;
}
public void setCashFund(BigDecimal cashFund) {
this.cashFund = cashFund;
}
}
Custom validator
Here is where I try to inject the repository. The repository is always null if not when I disable Hibernate validation.
public class UserValidator implements ConstraintValidator<ValidUser, User> {
private Logger log = LogManager.getLogger();
#Autowired
private UserRepository userRepository;
#Override
public void initialize(ValidUser constraintAnnotation) {
}
#Override
public boolean isValid(User value, ConstraintValidatorContext context) {
try {
User foundUser = userRepository.findByUsername(value.getUsername());
if (foundUser != null && foundUser.getId() != value.getId()) {
context.disableDefaultConstraintViolation();
context.buildConstraintViolationWithTemplate("{ValidUser.unique.username}").addConstraintViolation();
return false;
}
} catch (Exception e) {
log.error("", e);
return false;
}
return true;
}
}
messages.properties
#CUSTOM VALIDATORS
ValidUser.message = I dati inseriti non sono validi. Verificare nuovamente e ripetere l'operazione.
ValidUser.unique.username = L'username [${validatedValue.getUsername()}] è già stato utilizzato. Sceglierne un altro e ripetere l'operazione.
#DEFAULT VALIDATORS
org.hibernate.validator.constraints.NotBlank.message = Il campo non può essere vuoto
# === USER ===
Pattern.user.landlinePhone = Il numero di telefono non è valido. Dovrebbe essere nel formato E.123 internazionale (https://en.wikipedia.org/wiki/E.123)
In my tests, you can try from the source code, I've two problems:
The injected repository inside UserValidator is null if I don't disable Hibernate validation (spring.jpa.properties.javax.persistence.validation.mode=none)
Even if I disable Hibernate validator, my test cases fail because something prevent Spring to use the default string interpolation for validation messages that should be something like [Constraint].[class name lowercase].[propertyName]. I don't want to use the constraint annotation with the value element like this #NotBlank(message="{mycustom.message}") because I don't see the point considering that has his own convetion for interpolation and I can take advantage of that...that means less coding.
I attach the code; you can just run Junit tests and see errors (Hibernate validation is enable, check application.properties).
What am I doing wrong? What could I do to solve those two problems?
====== UPDATE ======
Just to clarify, reading Spring validation documentation https://docs.spring.io/spring/docs/current/spring-framework-reference/core.html#validation-beanvalidation-spring-constraints they say:
By default, the LocalValidatorFactoryBean configures a SpringConstraintValidatorFactory that uses Spring to create ConstraintValidator instances. This allows your custom ConstraintValidators to benefit from dependency injection like any other Spring bean.
As you can see, a ConstraintValidator implementation may have its dependencies #Autowired like any other Spring bean.
In my configuration class I created my LocalValidatorFactoryBean as they write.
Another interesting questions are this and this, but I had not luck with them.
====== UPDATE 2 ======
After a lot of reseach, seems with Hibernate validator the injection is not provided.
I found a couple of way you can do that:
1st way
Create this configuration class:
#Configuration
public class HibernateValidationConfiguration extends HibernateJpaAutoConfiguration {
public HibernateValidationConfiguration(DataSource dataSource, JpaProperties jpaProperties,
ObjectProvider<JtaTransactionManager> jtaTransactionManager,
ObjectProvider<TransactionManagerCustomizers> transactionManagerCustomizers) {
super(dataSource, jpaProperties, jtaTransactionManager, transactionManagerCustomizers);
}
#Autowired
private Validator validator;
#Override
protected void customizeVendorProperties(Map<String, Object> vendorProperties) {
super.customizeVendorProperties(vendorProperties);
vendorProperties.put("javax.persistence.validation.factory", validator);
}
}
2nd way
Create an utility bean
#Service
public class BeanUtil implements ApplicationContextAware {
private static ApplicationContext context;
#Override
public void setApplicationContext(ApplicationContext applicationContext) throws BeansException {
context = applicationContext;
}
public static <T> T getBean(Class<T> beanClass) {
return context.getBean(beanClass);
}
}
and then in the validator initialization:
#Override
public void initialize(ValidUser constraintAnnotation) {
userRepository = BeanUtil.getBean(UserRepository.class);
em = BeanUtil.getBean(EntityManager.class);
}
very important
In both cases, in order to make the it works you have to "reset" the entity manager in this way:
#Override
public boolean isValid(User value, ConstraintValidatorContext context) {
try {
em.setFlushMode(FlushModeType.COMMIT);
//your code
} finally {
em.setFlushMode(FlushModeType.AUTO);
}
}
Anyway, I don't know if this is really a safe way. Probably it's not a good practice access to the persistence layer at all.
If you really need to use injection in your Validator try adding #Configurable annotation on it:
#Configurable(autowire = Autowire.BY_TYPE, dependencyCheck = true)
public class UserValidator implements ConstraintValidator<ValidUser, User> {
private Logger log = LogManager.getLogger();
#Autowired
private UserRepository userRepository;
// this initialize method wouldn't be needed if you use HV 6.0 as it has a default implementation now
#Override
public void initialize(ValidUser constraintAnnotation) {
}
#Override
public boolean isValid(User value, ConstraintValidatorContext context) {
try {
User foundUser = userRepository.findByUsername( value.getUsername() );
if ( foundUser != null && foundUser.getId() != value.getId() ) {
context.disableDefaultConstraintViolation();
context.buildConstraintViolationWithTemplate( "{ValidUser.unique.username}" ).addConstraintViolation();
return false;
}
} catch (Exception e) {
log.error( "", e );
return false;
}
return true;
}
}
From the documentation to that annotation:
Marks a class as being eligible for Spring-driven configuration
So this should solve your null problem. To make it work though, you would need to configure AspectJ... (Check how to use #Configurable in Spring for that)

JSR-303 errors not detected natively by using the #Valid annotation

How come the #Valid annotation does not catch my JSR-303 annotations natively, but do catch them using the following method:
WebConfig.java
#Bean
public ResourceBundleMessageSource messageSource() {
ResourceBundleMessageSource messageSource = new ResourceBundleMessageSource();
String[] strBaseNames = {
"resources.messages.layout.LayoutResources",
"resources.messages.layout.MenuResources",
"resources.messages.global.GlobalResources"
};
messageSource.setUseCodeAsDefaultMessage(true);
messageSource.setDefaultEncoding("UTF-8");
messageSource.setBasenames(strBaseNames);
return messageSource;
}
#Bean
public LocalValidatorFactoryBean jsr303Validator() {
LocalValidatorFactoryBean localValidatorFactoryBean = new LocalValidatorFactoryBean();
localValidatorFactoryBean.setValidationMessageSource(this.messageSource());
return localValidatorFactoryBean;
}
UserController.java
#Controller
#RequestMapping(value = "/security/user", method = RequestMethod.GET)
public class UserController {
#Autowired
private SecurityService securityService;
#Autowired
SessionObject sessionObject;
#Autowired
#Qualifier("jsr303Validator") Validator validator;
#RequestMapping(value = "/edit/validate", method = RequestMethod.POST)
public String validateEdit( #ModelAttribute #Valid User user,
BindingResult result,
Model model) {
String strViewName = "common/error";
validator.validate(user, result);
if(result.hasErrors()) {
strViewName = "user/userEdit";
} else {
... update work ...
strViewName = "user/success";
}
return strViewName;
}
Resource:
http://www.wenda.io/questions/2462924/convert-jsr-303-validation-errors-to-springs-bindingresult.html
If I only use the #Valid annotation, result.hasErrors() is always empty, so I end up having an ConstraintViolationException (see below), which is expected. But I would like to have it working using the #Valid annotation only, without having to autowire my jsr303validator bean in each controller I want to implement some JSR-303 validations.
Constraint Violation log trace
Caused by: javax.validation.ConstraintViolationException: Validation failed for classes [spring4base.model.security.User] during update time for groups [javax.validation.groups.Default, ]
List of constraint violations:[
ConstraintViolationImpl{interpolatedMessage='error.firstname.notnull', propertyPath=strFirstName, rootBeanClass=class spring4base.model.security.User, messageTemplate='error.firstname.notnull'}
ConstraintViolationImpl{interpolatedMessage='error.userid.notnull', propertyPath=strUserId, rootBeanClass=class spring4base.model.security.User, messageTemplate='error.userid.notnull'}
ConstraintViolationImpl{interpolatedMessage='error.lastname.notnull', propertyPath=strLastName, rootBeanClass=class spring4base.model.security.User, messageTemplate='error.lastname.notnull'}
]
User.java
#NotBlank(message = "error.userid.notnull")
#Size(min = 0, max = 14, message = "error.firstname.length")
private String strUserId = "";
#NotBlank(message = "error.firstname.notnull")
#Size(min = 0, max = 50, message = "error.firstname.length")
private String strFirstName = "";
#NotBlank(message = "error.lastname.notnull")
#Size(min = 0, max = 50, message = "error.lastname.length")
private String strLastName = "";
Thank you
The following was required in order to make it work in my case:
First, define a LocalValidatorFactoryBean in my Java Config class (WebAppContext.java in my case).
#Bean
public ResourceBundleMessageSource messageSource() {
ResourceBundleMessageSource messageSource = new ResourceBundleMessageSource();
String[] baseNames = {
"resources.messages.layout.LayoutResources",
"resources.messages.layout.MenuResources",
"resources.messages.option.AppConfigResources",
"resources.messages.global.GlobalResources",
"resources.messages.contact.ContactResources",
"resources.messages.currentsession.CurrentSessionResources",
"resources.messages.welcome.WelcomeResources",
"resources.messages.user.UserResources",
"resources.messages.role.RoleResources",
"resources.messages.profile.ProfileResources"
};
messageSource.setCacheSeconds(60);
messageSource.setFallbackToSystemLocale(false);
messageSource.setUseCodeAsDefaultMessage(true);
messageSource.setDefaultEncoding(StandardCharsets.UTF_8.name());
messageSource.setBasenames(baseNames);
return messageSource;
}
#Bean
public LocalValidatorFactoryBean jsr303Validator() {
LocalValidatorFactoryBean localValidatorFactoryBean = new LocalValidatorFactoryBean();
localValidatorFactoryBean.setValidationMessageSource(this.messageSource());
return localValidatorFactoryBean;
}
Then, in my controller, I autowire that validator like so:
#Autowired
private Validator jsr303Validator;
And then, the only way I have found to get my JSR-303 executed is to launch them by hand, like so:
#RequestMapping(value = "/update", method = RequestMethod.POST)
public String processUpdate(#ModelAttribute UserForm userForm,
BindingResult result,
RedirectAttributes redirectAttributes,
SessionStatus status) throws CheckedPersistenceException {
this.userFormValidator.validate(userForm, result);
if (!result.hasErrors()) {
this.jsr303Validator.validate(userForm, result);
}
if(result.hasErrors()) {
return this.UPDATE_VIEW;
}
...
}

Resources