Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

JSR303 custom validators being called twice

I am creating a website using Spring MVC and for persistence I am using Spring Data JPA with Hibernate 4 as my JPA provider. Validation is being handled at present with Hibernate Validator. I have a problem whereby my validators are being called twice and I can't figure out why. The main reason this is a problem is because the second time round, dependencies are not being autowired into the validator and I am getting a null pointer exception. The following is the sequence of calls leading up to the failure:

  1. The registration form is submitted and first the NotDefaultSectValidator is called and completes successfully for the 'whereDidYouHearAboutUs' field on the user object.
  2. The UniqueUsernameValidator is called next and completes successfully for the 'username' field validation.
  3. The 'addUserFromForm' method on the controller starts and finds no errors in the bindingResults object.
  4. The 'addUser' method is then called on the UserService class. This method reaches the line 'userRepository.save(user);' but never then runs the 'print.ln' line immediate afterwards. Stepping over this line takes be back to the 'NotDefaultSectValidator' breakpoint. This completes for the second time and I re-enter the second validator 'UniqueUsernameValidator '. Here I get a null pointer exception because for some reason Spring fails to Autowire in the DAO this second time.

Can anyone shed light on why the validators are being called twice and in particular, why stepping over line 'userRepository.save(user);' goes back into these validators?

Many thanks

Here is my user.java class

package com.dating.domain;

import java.util.HashSet;
import java.util.Set;

import javax.persistence.CascadeType;
import javax.persistence.Column;
import javax.persistence.Entity;
import javax.persistence.FetchType;
import javax.persistence.GeneratedValue;
import javax.persistence.GenerationType;
import javax.persistence.Id;
import javax.persistence.JoinColumn;
import javax.persistence.JoinTable;
import javax.persistence.ManyToMany;
import javax.persistence.PrePersist;
import javax.persistence.PreUpdate;
import javax.persistence.Table;
import javax.persistence.Transient;
import javax.validation.constraints.Pattern;
import javax.validation.constraints.Size;

import org.hibernate.annotations.Type;
import org.hibernate.validator.constraints.Email;
import org.hibernate.validator.constraints.NotEmpty;
import org.joda.time.LocalDate;
import org.springframework.format.annotation.DateTimeFormat;

import com.dating.annotation.NotDefaultSelect;
import com.dating.annotation.UniqueUsername;

@Entity
@Table(name = "dating.user")
public class User {

    @Id
    @Column(name = "id")
    @GeneratedValue(strategy = GenerationType.IDENTITY)
    private Long id;

    @Column(name = "username", unique = true)
    @NotEmpty
    @Pattern(regexp = "^[a-zA-Z0-9]*$")
    @UniqueUsername
    private String username;

    @Column(name = "password", nullable = false)
    @NotEmpty
    @Size(min = 8)
    private String password;

    @Column(name = "first_name", nullable = false)
    @NotEmpty
    private String firstName;

    @Column(name = "last_name", nullable = false)
    @NotEmpty
    private String lastName;

    @Transient
    private String fullName;

    @Column(name = "email", nullable = false)
    @NotEmpty
    @Email
    private String email;

    @Column(name = "gender", nullable = false)
    @NotEmpty
    private String gender;

    @Column(name = "date_of_birth", nullable = false)
    @Type(type = "org.jadira.usertype.dateandtime.joda.PersistentLocalDate")
    @DateTimeFormat(pattern = "dd/MM/yyyy")
    private LocalDate dateOfBirth;

    @Column(name = "join_date", nullable = false)
    @Type(type = "org.jadira.usertype.dateandtime.joda.PersistentLocalDate")
    private LocalDate joinDate;

    @Column(name = "where_did_you_hear_about_us", nullable = false)
    @NotDefaultSelect
    private String whereDidYouHearAboutUs;

    @Column(name = "enabled")
    private boolean enabled;

    @ManyToMany(fetch = FetchType.LAZY, cascade = CascadeType.ALL)
    @JoinTable(name = "dating.user_roles", joinColumns = { @JoinColumn(name = "user_id", nullable = false, updatable = false) }, inverseJoinColumns = { @JoinColumn(name = "role_id", nullable = false, updatable = false) })
    private Set<Role> roles = new HashSet<Role>();

    @Column(name = "created_time", nullable = false)
    @Type(type = "org.jadira.usertype.dateandtime.joda.PersistentLocalDate")
    private LocalDate createdTime;

    @Column(name = "modification_time", nullable = false)
    @Type(type = "org.jadira.usertype.dateandtime.joda.PersistentLocalDate")
    private LocalDate modificationTime;

    public Long getId() {
        return id;
    }

    public void setId(Long id) {
        this.id = id;
    }

    public String getUsername() {
        return username;
    }

    public void setUsername(String username) {
        this.username = username;
    }

    public String getPassword() {
        return password;
    }

    public void setPassword(String password) {
        this.password = password;
    }

    public String getFirstName() {
        return firstName;
    }

    public void setFirstName(String firstName) {
        this.firstName = firstName;
    }

    public String getLastName() {
        return lastName;
    }

    public void setLastName(String lastName) {
        this.lastName = lastName;
    }

    public String getFullName() {
        return firstName + " " + lastName;
    }

    public void setFullName(String fullName) {
        this.fullName = fullName;
    }

    public String getEmail() {
        return email;
    }

    public void setEmail(String email) {
        this.email = email;
    }

    public String getGender() {
        return gender;
    }

    public void setGender(String gender) {
        this.gender = gender;
    }

    public LocalDate getDateOfBirth() {
        return dateOfBirth;
    }

    public void setDateOfBirth(LocalDate dateOfBirth) {
        this.dateOfBirth = dateOfBirth;
    }

    public LocalDate getJoinDate() {
        return joinDate;
    }

    public void setJoinDate(LocalDate joinDate) {
        this.joinDate = joinDate;
    }

    public String getWhereDidYouHearAboutUs() {
        return whereDidYouHearAboutUs;
    }

    public void setWhereDidYouHearAboutUs(String whereDidYouHearAboutUs) {
        this.whereDidYouHearAboutUs = whereDidYouHearAboutUs;
    }

    public boolean isEnabled() {
        return enabled;
    }

    public void setEnabled(boolean enabled) {
        this.enabled = enabled;
    }

    public Set<Role> getRoles() {
        return roles;
    }

    public void setRoles(Set<Role> roles) {
        this.roles = roles;
    }

    public void addRole(Role role) {
        roles.add(role);
    }

    public LocalDate getCreatedTime() {
        return createdTime;
    }

    public void setCreatedTime(LocalDate createdTime) {
        this.createdTime = createdTime;
    }

    public LocalDate getModificationTime() {
        return modificationTime;
    }

    public void setModificationTime(LocalDate modificationTime) {
        this.modificationTime = modificationTime;
    }

    @PreUpdate
    public void preUpdate() {
        modificationTime = new LocalDate();
    }

    @PrePersist
    public void prePersist() {
        LocalDate now = new LocalDate();
        createdTime = now;
        modificationTime = now;
    }
}

The relevant method in my registration controller:

@RequestMapping(value = "/register", method = RequestMethod.POST)
public String addUserFromForm(@Valid User user,
        BindingResult bindingResult, RedirectAttributes ra) {
    if (bindingResult.hasErrors()) {
        return "user/register";
    }
    userService.addUser(user);

    // Redirecting to avoid duplicate submission of the form
    return "redirect:/user/" + user.getUsername();
}

My service class:

package com.dating.service.impl;

import javax.transaction.Transactional;

import org.joda.time.LocalDate;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.security.crypto.bcrypt.BCryptPasswordEncoder;
import org.springframework.stereotype.Service;

import com.dating.domain.Role;
import com.dating.domain.User;
import com.dating.repository.RoleRepository;
import com.dating.repository.UserRepository;
import com.dating.repository.specification.UserSpecifications;
import com.dating.service.UserService;

@Service
public class UserServiceImpl implements UserService {

    @Autowired
    private UserRepository userRepository;

    @Autowired
    private RoleRepository roleRepository;

    @Transactional
    @Override
    public void addUser(User user) {
        user.setJoinDate(new LocalDate());
        user.setEnabled(true);
        Role role = roleRepository.findByName(Role.MEMBER);
        if (role == null) {
            role = new Role();
            role.setName(Role.MEMBER);
        }
        user.addRole(role);
        BCryptPasswordEncoder encoder = new BCryptPasswordEncoder();
        user.setPassword(encoder.encode(user.getPassword()));
        userRepository.save(user);
        System.out.println("User Saved");
    }

    @Override
    public User getUserByUsername(String username) {
        return userRepository.findByUsername(username);
    }

    @Override
    public Iterable<User> getAllUsers() {
        return userRepository.findAll();
    }

    @Override
    public void updateDetails(User user) {
        userRepository.save(user);
    }

    @Override
    public Iterable<User> lastNameIsLike(String searchTerm) {
        return userRepository.findAll(UserSpecifications
                .lastNameIsLike(searchTerm));
    }
}

My NotDefaultSelect validator:

package com.dating.validator;

import javax.validation.ConstraintValidator;
import javax.validation.ConstraintValidatorContext;
import com.dating.annotation.NotDefaultSelect;

public class NotDefaultSelectValidator implements
        ConstraintValidator<NotDefaultSelect, String> {
    @Override
    public void initialize(NotDefaultSelect constraint) {

    }

    @Override
    public boolean isValid(String selectedValue, ConstraintValidatorContext ctx) {
        if (selectedValue == null) {
            return false;
        }
        if (selectedValue.equals("") || selectedValue.equals("0")
                || selectedValue.equalsIgnoreCase("default")
                || selectedValue.equalsIgnoreCase("please select")) {
            return false;
        }
        return true;
    }

}

My uniqueUsername validator:

package com.dating.validator;

import javax.validation.ConstraintValidator;
import javax.validation.ConstraintValidatorContext;

import org.springframework.beans.factory.annotation.Autowired;

import com.dating.annotation.UniqueUsername;
import com.dating.repository.UserRepository;

public class UniqueUsernameValidator implements
        ConstraintValidator<UniqueUsername, String> {

    @Autowired
    private UserRepository userRepository;

    @Override
    public void initialize(UniqueUsername constraint) {

    }

    @Override
    public boolean isValid(String username, ConstraintValidatorContext ctx) {
        if (username == null || userRepository.findByUsername(username) == null) {
            return true;
        }
        return false;
    }

}

My UserRepository:

package com.dating.repository;

import org.springframework.data.jpa.repository.JpaSpecificationExecutor;
import org.springframework.data.repository.CrudRepository;

import com.dating.domain.User;

//Spring Data JPA Marker interfaces being extended for automatic CRUD repository creation
public interface UserRepository extends CrudRepository<User, Long>, JpaSpecificationExecutor<User> {

    //Automatic query creation from method name
    public User findByUsername(String username);
}

Lastly my persistence-context.xml file

<!-- Data source properties -->
<util:properties id="dataSourceSettings" location="classpath:datasource.properties" />

<!-- Pooled data source using BoneCP -->
<bean id="dataSource" class="com.jolbox.bonecp.BoneCPDataSource"
    destroy-method="close">
    <property name="driverClass" value="#{dataSourceSettings['jdbc.driverClass']}" />
    <property name="jdbcUrl" value="#{dataSourceSettings['jdbc.url']}" />
    <property name="username" value="#{dataSourceSettings['jdbc.username']}" />
    <property name="password" value="#{dataSourceSettings['jdbc.password']}" />
    <property name="idleConnectionTestPeriodInMinutes" value="60" />
    <property name="idleMaxAgeInMinutes" value="240" />
    <property name="maxConnectionsPerPartition" value="30" />
    <property name="minConnectionsPerPartition" value="10" />
    <property name="partitionCount" value="3" />
    <property name="acquireIncrement" value="5" />
    <property name="statementsCacheSize" value="100" />
    <property name="releaseHelperThreads" value="3" />
</bean>

<!-- JPA entity manager factory bean -->
<bean id="entityManagerFactory"
    class="org.springframework.orm.jpa.LocalContainerEntityManagerFactoryBean">
    <property name="dataSource" ref="dataSource" />
    <property name="packagesToScan" value="com.dating.domain" />
    <property name="jpaVendorAdapter">
        <bean class="org.springframework.orm.jpa.vendor.HibernateJpaVendorAdapter" />
    </property>
    <property name="jpaProperties">
        <props>
            <prop key="hibernate.dialect">#{dataSourceSettings['hibernate.dialect']}</prop>
            <prop key="hibernate.hbm2ddl.auto">#{dataSourceSettings['hibernate.hbm2ddl.auto']}
            </prop>
            <prop key="hibernate.show_sql">#{dataSourceSettings['hibernate.show_sql']}</prop>
            <prop key="hibernate.format_sql">#{dataSourceSettings['hibernate.format_sql']}</prop>
            <prop key="hibernate.use_sql_comments">#{dataSourceSettings['hibernate.use_sql_comments']}
            </prop>
        </props>
    </property>
</bean>

<tx:annotation-driven transaction-manager="transactionManager" />

<bean id="transactionManager" class="org.springframework.orm.jpa.JpaTransactionManager">
    <property name="entityManagerFactory" ref="entityManagerFactory" />
</bean>

<context:annotation-config />

<jpa:repositories base-package="com.dating.repository"/>
like image 424
F Sohail Avatar asked Jul 25 '14 12:07

F Sohail


1 Answers

You can just add this property in your application.property files to disable hibernate validation spring.jpa.properties.javax.persistence.validation.mode=none

like image 59
Vikky Avatar answered Oct 18 '22 07:10

Vikky