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

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

Related

Why my DAO classes are not scanned?

I've been stuck for a week with this problem and there is no way to find a solution in my project using Spring tool suite.
My dispatcher scan packages and creates beans for the controller and service layer but it seems it can't reach the model layer.
This is my servlet-context.xml:
<beans:bean id="sessionFactory" class="org.springframework.orm.hibernate4.LocalSessionFactoryBean">
<!-- <beans:property name="dataSource" ref="dataSource"/> -->
<beans:property name="configLocation" value="classpath:hibernate-annotation.cfg.xml" />
</beans:bean>
<tx:annotation-driven/>
<!-- <beans:bean id="transactionManager" class="org.springframework.orm.hibernate4.HibernateTransactionManager">
<beans:property name="sessionFactory" ref="sessionFactory" />
</beans:bean> -->
I have commented the transactionManager bean because it throws an expection.
The problem comes up when in my UserServiceImpl set UserDaoI as #Autowired
#Service
public class UserServiceImpl implements UserServiceI{
private static final Logger logger =
Logger.getLogger(UserServiceImpl.class);
#Autowired
UserDaoI userDao;
public boolean isRegisteredUser(User user){
logger.debug("Entrando en isRegisteredUser" + logger.getClass());
boolean isRegistered = false;
UserDao uDao = userDao.getUserByDni(user.getDni());
if(!(uDao.getEmail().equals(user.getEmail()))){
isRegistered = true;
}
return isRegistered;
}
I am clueless and desperate.
EDIT:
This is my UserDaoI class, but I think is not relevant.
public interface UserDaoI {
void addUser(UserDao userDao);
UserDao getUser(int id);
boolean updateUser(UserDao userDao);
boolean deleteUser(int id);
List<UserDao> getAllUsers();
UserDao getUserByDni(String dni);
}
This is my UserDaoImpl.java, the source of the problems.
#Repository
public class UserDaoImpl implements UserDaoI{
private static final Logger logger = LoggerFactory.getLogger(UserDaoImpl.class);
#Autowired
SessionFactory sessionFactory;
Session session;
#Override
public void addUser(UserDao userDao) {
session = sessionFactory.openSession();
session.beginTransaction();
session.save(userDao);
session.getTransaction().commit();
session.close();
}
#Override
public UserDao getUser(int id) {
//sessionFactory = HibernateUtil.getSessionFactory();
session = sessionFactory.openSession();
session.beginTransaction();
UserDao userDao = (UserDao) session.get(UserDao.class, id);
session.getTransaction().commit();
session.close();
return userDao;
}
#Override
public boolean updateUser(UserDao userDao) {
assert(userDao!=null);
//sessionFactory = HibernateUtil.getSessionFactory();
session = sessionFactory.openSession();
session.beginTransaction();
session.update(userDao);
session.getTransaction().commit();
session.close();
return false;
}
#Override
public boolean deleteUser(int id) {
//sessionFactory = HibernateUtil.getSessionFactory();
session = sessionFactory.getCurrentSession();
UserDao userDao = new UserDao();
userDao.setId(id);
if(getUser(id)!=null){
try{
session.beginTransaction();
session.delete(userDao);
session.getTransaction().commit();
return true;
}catch(Exception ex){
logger.error("No se ha podido borrar el usuario");
}finally{
session.close();
}
}
return false;
}
#Override
public List<UserDao> getAllUsers() {
//sessionFactory = HibernateUtil.getSessionFactory();
session = sessionFactory.openSession();
session.beginTransaction();
#SuppressWarnings("unchecked")ArrayList<UserDao> userDaoList = (ArrayList<UserDao>) session.createQuery("from user").list();
session.getTransaction().commit();
session.close();
return userDaoList;
}
#Override
public UserDao getUserByDni(String dni) {
session = sessionFactory.getCurrentSession();
session.beginTransaction();
UserDao userDao = (UserDao) session.createQuery("from user where dni = " + dni).uniqueResult();
session.getTransaction().commit();
session.close();
return userDao;
}
}
Why I do not need to autowire my Session?
This is my hibernate config
<hibernate-configuration>
<session-factory>
<property name="hibernate.dialect">org.hibernate.dialect.MySQLDialect</property>
<property name="hibernate.connection.driver_class">com.mysql.jdbc.Driver</property>
<property name="hibernate.connection.url">jdbc:mysql://localhost:3306/gen</property>
<property name="hibernate.connection.username">root</property>
<property name="hibernate.connection.password">fiw48asi</property>
<property name="hibernate.current_session_context_class">thread</property>
<property name="hibernate.hbm2ddl.auto">update</property>
<property name="show_sql">true</property>
<mapping class="com.library.app.dao.user.UserDao"/>
</session-factory>
Now the exception I get is
org.hibernate.hql.internal.ast.QuerySyntaxException: user is not mapped
But it is declared in hibernate-annotation.cfg.xml
Thanks a ton!
The problem is:
'userDaoImpl': Injection of autowired dependencies failed...: Could not autowire field:
because
No qualifying bean of type [org.hibernate.SessionFactory] found for dependency: expected at least 1 bean which qualifies as autowire candidate
In your model, you will need to define a userDaoImpl bean with an #Bean annotation and a Hibernate SessionFactory to allow Spring to create and inject it, e.g.,: add a method to your UserServiceImpl class like this:
UserServiceImpl.java
#Bean
public UserDaoI userDao() {
return new UserDaoImpl();
}
But I notice some mixing of types and models, however, which I think is at the root of your issues, so I'll explain how the service with DAO model works generally with a simpler model.
In a "service and data access object" model, you create a persistence mediator for each entity, conventionally with a DAO suffix and the entity is typically a simple plain ordinary Java object (POJO).
For example, to represent an in-memory user, you might model the entity as follows:
User.java
public class User {
private String name;
// more POJO properties as need..
public void setName(String name) {
this.name = name;
}
public String getName(String name) {
return this.name;
}
// more POJO setters and getters...
}
Now we need a mediator which can create, read, update, and delete these entities from our database. In a DAO pattern, that's the role of the entity's DAO:
UserDao.java
import org.hibernate.SessionFactory;
import org.springframework.beans.factory.annotation.Autowired;
public class UserDao {
#Autowired
private SessionFactory sessionFactory;
public User getUser(long id) {
sessionFactory.getCurrentSession().get(User.class, id);
}
public void addUser(User user) {
sessionFactory.getCurrentSession().saveOrUpdate(user);
}
//...
}
We add a service layer on top of the DAO layer when services either span entities or add some (likely to change) business logic to the entity. For example, supposed we have a registration table which captures registration records in various states for each user:
UserService.java
public class UserService {
#Autowired
private UserDao userDao
#Autowired
private RegistrationDao registrationDao
public boolean isRegistered(long userId) {
// DAOs mediate access to tables
User user = userDao.getUser(userId);
Registration reg = registrationDao.getRegistration();
//
// business logic
//
return isUserRegistered(user, registration);
}
}
With Spring 4, though, I don't hand-code my DAO layers anymore; instead I use Spring Data JPA with the Hibernate JPA provider. Here's a very simple example of how to make this work. You're already using the Hibernate ORM, so you don't "lose" much (quite the opposite, really) by using Spring Data JPA with the Hibernate JPA provider.

How to get the Invalid value in the error message while using custom validator in spring?

I having trouble in showing Invalid value in the error message
I have messages.properties file as follows
ProductId.exists.custom.validator.validation = A product already exists with product id ${validatedValue}.
and following is my custom validtor interface
#Target( { METHOD, FIELD, ANNOTATION_TYPE })
#Retention(RUNTIME)
#Constraint(validatedBy = ProductIdValidator.class)
#Documented
public #interface ProductId {
String message() default "{ProductId.exists.custom.validator.validation}";
Class<?>[] groups() default {};
public abstract Class<? extends Payload>[] payload() default {};
}
Here is the implementation
#Component
public class ProductIdValidator implements ConstraintValidator<ProductId, String>{
#Autowired
private ProductService productService;
#Override
public void initialize(ProductId constraintAnnotation) {
// TODO Auto-generated method stub
}
#Override
public boolean isValid(String value, ConstraintValidatorContext context) {
Product product = productService.getProductById(value);
if(product!= null) {
return false;
}
return true;
}
}
When I am running my application I am getting error message like
A product already exists with product id ${validatedValue}. but I am expecting A product already exists with product id P1234. as error message
How to get the validatedValue in error message?
Additional details
I have used hibernate-validator:4.3.1.Final version and spring-webmvc:3.2.0.RELEASE
And I am triggering the validation in the context as follows
<mvc:annotation-driven validator="validator"/>
<bean id="validator" class="org.springframework.validation.beanvalidation.LocalValidatorFactoryBean">
<property name="validationMessageSource" ref="messageSource" />
</bean>
I’m not sure what was the main reason for this solution I’ve used in my project – if to make interpolation work, or just use ReloadableResourceBundleMessageSource (that supports properties in UTF-8 and runtime reloading!) instead of the default one. However, this should work for you.
<mvc:annotation-driven validator="validator" />
<bean id="validator" class="org.springframework.validation.beanvalidation.LocalValidatorFactoryBean" primary="true"
p:messageInterpolator-ref="messageInterpolator" />
<!-- Hibernate Validator which can interpolate the value being validated in the constraint message -->
<bean id="messageInterpolator" class="ValueFormatterMessageInterpolatorFactoryBean"
p:messageSource-ref="validatorMessageSource" />
<bean id="validatorMessageSource" class="org.springframework.context.support.ReloadableResourceBundleMessageSource"
p:basename="classpath:/config/i18n/validator-messages"
p:defaultEncoding="utf-8"
p:cacheSeconds="0" />
And the custom ValueFormatterMessageInterpolatorFactoryBean class:
/**
* {#linkplain FactoryBean} that creates {#link ValueFormatterMessageInterpolator}
* with underlying {#link ResourceBundleMessageInterpolator} that uses the given
* {#link MessageSource}.
*/
public class ValueFormatterMessageInterpolatorFactoryBean implements FactoryBean<MessageInterpolator> {
private MessageSource messageSource;
public MessageInterpolator getObject() throws Exception {
ResourceBundleLocator resourceBundleLocator = new MessageSourceResourceBundleLocator(messageSource);
return new ValueFormatterMessageInterpolator(
new ResourceBundleMessageInterpolator(resourceBundleLocator));
}
public Class<?> getObjectType() {
return ValueFormatterMessageInterpolator.class;
}
public boolean isSingleton() {
return true;
}
#Required
public void setMessageSource(MessageSource messageSource) {
this.messageSource = messageSource;
}
}
Note: I’m using Hibernate Validator 4.3.0.Final.

Spring data MongoDb cannot convert proxy bean

I'm using Spring AOP with AspectJ and Spring Data MongoDb and am having a world of trouble persisting objects.
In this case, I have an AclEntryDaoImpl that exposes AclEntryImpl. When AclEntryImpl is provided a Principal that is a standard Java object (a "non-Spring" bean), mongoTemplate.save() works as expected. However when Principal is a Spring bean, Mongo is unable to convert the object and results in a MappingException org.springframework.data.mapping.model.MappingException: No id property found on class class com.sun.proxy.$Proxy33. All my objects need to be Spring beans so that (a) I keep my objects decoupled and (b) my AOP (LoggingAspect) is invoked.
Lastly, I cannot take advantage of Spring converters because Mongo sees the target object AclEntryImpl as a proxy com.sun.proxy.$Proxy33 and so Converter<Principal, DBObject> is never invoked.
Any and all help would be greatly appreciated!
Snippets:
Here's my Spring XML configuration:
<beans>
<context:component-scan base-package="a.b" />
<context:property-placeholder location="config.properties" />
<aop:aspectj-autoproxy />
<bean id="loggingAspect" class="a.b.LoggingAspect" />
<mongo:db-factory host="${database.host}" port="${database.port}" dbname="${database.dbname}" />
<bean id="mongoTemplate" class="org.springframework.data.mongodb.core.MongoTemplate">
<constructor-arg name="mongoDbFactory" ref="mongoDbFactory" />
</bean>
<bean id="aclEntryDao" class="a.b.AclEntryDaoImpl">
<lookup-method name="createAclEntry" bean="aclEntry" />
</bean>
</beans>
AclEntryImpl:
#Document
#Component
#Scope("prototype")
public class AclEntryImpl implements AclEntry {
#Id
private String id;
private String service;
#DBRef #Expose
private Principal principal;
#Expose
private boolean accessGranted;
#Expose
private List<Permission> permissions;
#Override #Loggable #MongoSaveReturned
public AclEntry save() {
return this;
}
...getters and setters...
}
AclEntryDaoImpl:
#Repository
public abstract class AclEntryDaoImpl implements AclEntryDao {
#Override #Loggable
public AclEntry addEntry(String serviceName, Principal principal, Permission[] permissions, boolean accessGranted) throws Exception {
AclEntry entry = createAclEntry(); //<-- Spring lookup-method
entry.setService(serviceName);
entry.setPrincipal(principal); //<-- com.sun.proxy.$Proxy33
entry.setAccessGranted(accessGranted);
for (Permission permission : permissions) {
if (!entry.addPermission(permission)) {
return null;
}
}
return entry.save();
}
... other DAO methods ...
}
LoggingAspect:
#Aspect
public class LoggingAspect {
#Autowired
private MongoTemplate mongoTemplate;
#Pointcut("execution(!void a.b..*.*(..))")
public void returningMethods() {}
#AfterReturning(pointcut="returningMethods() && #annotation(MongoSaveReturned)", returning="retVal")
public Object mongoSaveReturnedAdvice(Object retVal) {
Logger logger = null;
try {
logger = getLogger(retVal);
mongoTemplate.save(retVal); //<-- throws MappingException
log(logger, "save: " + retVal.toString());
} catch (Exception e) {
log(logger, "throw: " + e.toString());
}
return retVal;
}
... other logging methods ...
}

Using Spring's LocalValidatorFactoryBean with JSF

I am trying to get a bean injected into a custom ConstraintValidator. I have come across some things:
CDI is supported in validation-api-1.1.0 (Beta available)
Hibernate Validator 5 seems to implement validation-api-1.1.0 (Alpha available)
Use Seam validation module
Use Spring's LocalValidatorFactoryBean
The last one seems most appropriate for my situation since we're already using Spring (3.1.3.Release).
I have added the validator factory to the XML application context and annotations are enabled:
<beans xmlns="http://www.springframework.org/schema/beans"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xmlns:context="http://www.springframework.org/schema/context"
xsi:schemaLocation="http://www.springframework.org/schema/beans
http://www.springframework.org/schema/beans/spring-beans-3.1.xsd
http://www.springframework.org/schema/context
http://www.springframework.org/schema/context/spring-context-3.1.xsd">
<context:component-scan base-package="com.example" />
<bean id="validator" class="org.springframework.validation.beanvalidation.LocalValidatorFactoryBean" />
</beans>
The validator:
public class UsernameUniqueValidator implements
ConstraintValidator<Username, String>
{
#Autowired
private PersonManager personManager;
#Override
public void initialize(Username constraintAnnotation)
{
}
#Override
public boolean isValid(String value, ConstraintValidatorContext context)
{
if (value == null) return true;
return personManager.findByUsername(value.trim()) != null;
}
}
The validation is applied to a Person:
public class Person
{
#Username
private String username;
}
And the backing bean:
#Named
#Scope("request")
public class PersonBean
{
private Person person = new Person();
#Inject
private PersonManager personManager;
public create()
{
personManager.create(person);
}
}
And in the JSF page I have:
<p:inputText value="#{personBean.person.username}" />
The validator is invoked but the field is not autowired/injected and stays null. This of course trows a NullPointerException.
I am testing this with Hibernate validator 4.2 (since LocalValidatorFactoryBean should be able to do this I think).
I also faced the same issue. In my case Spring+MyFaces+RichFaces are used. During the application startup Spring creates it's LocalValidatorFactoryBean, but MyFaces doesn't use that bean as a validation factory. Instead MyFaces and RichFaces both used their own validators even with spring-faces module.
To figure out how to make faces use LocalValidatorFactoryBean I looked inside javax.faces.validator.BeanValidator createValidatorFactory method. This method is used by MyFaces to create ValidatorFactory every time when validation is required. Inside of that method you can see the following:
Map<String, Object> applicationMap = context.getExternalContext().getApplicationMap();
Object attr = applicationMap.get(VALIDATOR_FACTORY_KEY);
if (attr instanceof ValidatorFactory)
{
return (ValidatorFactory) attr;
}
else
{
synchronized (this)
{
if (_ExternalSpecifications.isBeanValidationAvailable())
{
ValidatorFactory factory = Validation.buildDefaultValidatorFactory();
applicationMap.put(VALIDATOR_FACTORY_KEY, factory);
return factory;
}
else
{
throw new FacesException("Bean Validation is not present");
}
}
}
So as you can see it first tries to load ValidatorFactory from context before creating a new instance. So I implemented the following solution to make faces use Spring LocalValidatorFactoryBean: I created a SystemEventListener which runs on PostConstructApplicationEvent. This listener get's a Spring WebApplicationContext from servlet context, retrieves instance of LocalValidatorFactoryBean from it and stores it in ExternalContext ApplicationMap.
public class SpringBeanValidatorListener implements javax.faces.event.SystemEventListener {
private static final long serialVersionUID = -1L;
private final Logger logger = LoggerFactory.getLogger(SpringBeanValidatorListener.class);
#Override
public boolean isListenerForSource(Object source) {
return true;
}
#Override
public void processEvent(SystemEvent event) {
if (event instanceof PostConstructApplicationEvent) {
FacesContext facesContext = FacesContext.getCurrentInstance();
onStart(facesContext);
}
}
private void onStart(FacesContext facesContext) {
logger.info("--- onStart ---");
if (facesContext == null) {
logger.warn("FacesContext is null. Skip further steps.");
return;
}
ServletContext context = getServletContext(facesContext);
if (context == null) {
logger.warn("ServletContext is not available. Skip further steps.");
return;
}
WebApplicationContext webApplicationContext = (WebApplicationContext) context.getAttribute(WebApplicationContext.ROOT_WEB_APPLICATION_CONTEXT_ATTRIBUTE);
if (webApplicationContext == null) {
logger.warn("Spring WebApplicationContext was not set in ServletContext. Skip further steps.");
return;
}
LocalValidatorFactoryBean validatorFactory = null;
try {
validatorFactory = webApplicationContext.getBean(LocalValidatorFactoryBean.class);
} catch (BeansException ex){
logger.warn("Cannot get LocalValidatorFactoryBean from spring context.", ex);
}
logger.info("LocalValidatorFactoryBean loaded from Spring context.");
Map<String, Object> applicationMap = facesContext.getExternalContext().getApplicationMap();
applicationMap.put(BeanValidator.VALIDATOR_FACTORY_KEY, validatorFactory);
logger.info("LocalValidatorFactoryBean set to faces context.");
}
private ServletContext getServletContext(FacesContext facesContext) {
return (ServletContext) facesContext.getExternalContext().getContext();
}
}
So when MyFaces try to get ValidatorFactory for the first time, LocalValidatorFactoryBean is already there and MyFaces don't create a new instance.
It is definately the way to go to add your own custom ValidatorFactory to the application map using the key BeanValidator.VALIDATOR_FACTORY_KEY.
But instead of using a javax.faces.event.SystemEventListener, you could also approach it from the spring side. Registering your ValidatorFactory as an attribute in the ServletContext will be enough for it to be picked up and added to the application map (which is an abstraction for either the ServletContext or PortletContext, whatever you are using).
So the question is: how to add a spring bean as an attribute to the ServletContext. My solution was to use a helper bean that implements ServletContextAware:
public class ServletContextAttributePopulator implements ServletContextAware {
Map<String,Object> attributes;
public Map<String, Object> getAttributes() {
return attributes;
}
public void setAttributes(Map<String, Object> attributes) {
this.attributes = attributes;
}
#Override
public void setServletContext(ServletContext servletContext) {
for (Map.Entry<String,Object> entry : attributes.entrySet()) {
servletContext.setAttribute(entry.getKey(), entry.getValue());
}
}
}
Note that you could use this class for any type of bean you want to add to the ServletContext.
In your spring context, you would then add:
<bean id="servletContextPopulator" class="my.package.ServletContextAttributePopulator">
<property name="attributes">
<map>
<entry key="javax.faces.validator.beanValidator.ValidatorFactory" value-ref="validator"/>
</map>
</property>
</bean>
where "validator" is the id of your LocalValidatorFactoryBean.
I am not a spring expert, but I would expect that you either define a PersonManager in beans.xml as well or that you annotate it with #Component. See also Autowiring Unmanaged Beans Annotated With #Component

Can spring mvc trim all strings obtained from forms?

I know struts2 default config will trim all strings obtained from forms.
For example:
I type " whatever " in a form and submit, I will get "whatever" The string has been auto trimmed
Does spring mvc have this function too? THX.
Using Spring 3.2 or greater:
#ControllerAdvice
public class ControllerSetup
{
#InitBinder
public void initBinder ( WebDataBinder binder )
{
StringTrimmerEditor stringtrimmer = new StringTrimmerEditor(true);
binder.registerCustomEditor(String.class, stringtrimmer);
}
}
Testing with an MVC test context:
#RunWith(SpringJUnit4ClassRunner.class)
#WebAppConfiguration
#ContextConfiguration
public class ControllerSetupTest
{
#Autowired
private WebApplicationContext wac;
private MockMvc mockMvc;
#Before
public void setup ( )
{
this.mockMvc = MockMvcBuilders.webAppContextSetup(this.wac).build();
}
#Test
public void stringFormatting ( ) throws Exception
{
MockHttpServletRequestBuilder post = post("/test");
// this should be trimmed, but only start and end of string
post.param("test", " Hallo Welt ");
ResultActions result = mockMvc.perform(post);
result.andExpect(view().name("Hallo Welt"));
}
#Configuration
#EnableWebMvc
static class Config
{
#Bean
TestController testController ( )
{
return new TestController();
}
#Bean
ControllerSetup controllerSetup ( )
{
return new ControllerSetup();
}
}
}
/**
* we are testing trimming of strings with it.
*
* #author janning
*
*/
#Controller
class TestController
{
#RequestMapping("/test")
public String test ( String test )
{
return test;
}
}
And - as asked by LppEdd - it works with passwords too as on the server side there is no difference between input[type=password] and input[type=text]
register this property editor:
org.springframework.beans.propertyeditors.StringTrimmerEditor
Example for AnnotionHandlerAdapter:
<bean class="org.springframework.web.servlet.mvc.annotation.AnnotationMethodHandlerAdapter">
...
<property name="webBindingInitializer">
<bean class="org.springframework.web.bind.support.ConfigurableWebBindingInitializer">
<property name="propertyEditorRegistrar">
<bean class="org.springframework.beans.propertyeditors.StringTrimmerEditor" />
</property>
</bean>
</property>
...
</bean>
You can also use Spring's conversion service, which has the added benefit of working with <mvc:annotation-driven/> and with Spring Webflow. As with the other answers, the major downside is that this is a global change and can't be disabled for certain forms.
You'll need a converter to do the trimming
public class StringTrimmingConverter implements Converter<String, String> {
#Override
public String convert(String source) {
return source.trim();
}
}
Then define a conversion service that knows about your converter.
<bean id="applicationConversionService" class="org.springframework.format.support.FormattingConversionServiceFactoryBean">
<property name="converters">
<list>
<bean class="mypackage.util.StringTrimmingConverter"/>
</list>
</property>
</bean>
and tie that in to mvc.
<mvc:annotation-driven conversion-service="applicationConversionService"/>
If you use Spring Webflow then it require a wrapper
<bean id="defaultConversionService" class="org.springframework.binding.convert.service.DefaultConversionService">
<constructor-arg ref="applicationConversionService"/>
</bean>
and a setting on your flow builder
<flow:flow-builder-services id="flowBuilderServices" conversion-service="defaultConversionService" development="true" validator="validator" />
Just customized the above code in order to adjust to Spring Boot, if you want to explicit trim function for some fields in the form, you can show them as below:
#Component
#ControllerAdvice
public class ControllerSetup {
#InitBinder({"dto", "newUser"})
public void initBinder(WebDataBinder binder) {
binder.registerCustomEditor(String.class, new StringTrimmerEditor(true));
binder.registerCustomEditor(String.class, "userDto.username", new StringTrimmerEditor(false));
binder.registerCustomEditor(String.class, "userDto.password", new DefaultStringEditor(false));
binder.registerCustomEditor(String.class, "passwordConfirm", new DefaultStringEditor(false));
}
}
You can user a Spring-MVC Interceptor
public class TrimInterceptor extends HandlerInterceptorAdapter {
#Override
public boolean preHandle(HttpServletRequest request, HttpServletResponse response, Object handler) throws Exception {
Enumeration<String> e = request.getParameterNames();
while(e.hasMoreElements()) {
String parameterName = e.nextElement();
request.setParameter(parameterName, request.getParameter(parameterName).trim());
}
return true;
}
And set up your HandlerMapping interceptors property
<bean id="interceptorTrim" class="br.com.view.interceptor.TrimInterceptor"/>
<bean class="org.springframework.web.servlet.handler.BeanNameUrlHandlerMapping" p:interceptors-ref="interceptorTrim"/>
}
Or use a Servlet Filter
first,trim requestparam which is String,you can create a class and implimplements WebBingdingInitializer
#ControllerAdvice
public class CustomWebBindingInitializer implements WebBindingInitializer {
#InitBinder
#Override
public void initBinder(WebDataBinder webDataBinder, WebRequest webRequest) {
webDataBinder.registerCustomEditor(String.class, new StringTrimmerEditor(true));
}
}
please use componentScan make this Class to be a Spring Bean.
But, I don't know how to trim the String value in requestBody JSON data.

Resources