Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

What is the best way to implement custom validation in spring application?

I'm (new in spring development) creating REST API for my application, CRUD operations are implemented successfully but now I want to implement server side validation. I've also read that there are several ways through which validation could be implemented.

  1. Using given annotations -> @notempty, @email, etc...
  2. Using custom validation -> extending validators

I want to implement both of them in my application, With reference to that,

is it good approach to follow?

OR

Is there any other ways through which validation can be implemented?


Controller

@RestController
public class EmployeeController {

    @Autowired
    DataServices dataServices;

    @Autowired
    EmployeeValidator employeeValidator;

    @InitBinder
    protected void initBinder(WebDataBinder binder) {
        binder.addValidators(employeeValidator);
    }

    @RequestMapping(value = "/employee/", method = RequestMethod.POST)
    public ResponseEntity<Object> createUser(
            @Valid @RequestBody Employee employee,
            UriComponentsBuilder ucBuilder) throws Exception,
            DataIntegrityViolationException {

        if (dataServices.addEmployee(employee) == 0) {
            Error error = new Error(1, "Data integrity violation",
                    "Email id is already exists.");
            return new ResponseEntity<Object>(error, HttpStatus.CONFLICT);
        }

        HttpHeaders headers = new HttpHeaders();
        headers.setLocation(ucBuilder.path("/employee/{id}")
                .buildAndExpand(employee.getId()).toUri());
        Status status = new Status(1, "Employee has been added successfully.");

        return new ResponseEntity<Object>(status, headers, HttpStatus.CREATED);
    }
}

Error Handler

@ControllerAdvice
public class RestErrorHandler {

    private static final Logger logger = Logger
            .getLogger(RestErrorHandler.class);

    private MessageSource messageSource;

    @Autowired
    public RestErrorHandler(MessageSource messageSource) {
        this.messageSource = messageSource;
    }

    @ExceptionHandler(MethodArgumentNotValidException.class)
    @ResponseStatus(HttpStatus.BAD_REQUEST)
    @ResponseBody
    public ValidationErrorDTO processValidationError(
            MethodArgumentNotValidException ex) {
        logger.debug("Handling form validation error");

        BindingResult result = ex.getBindingResult();
        List<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.getField(), localizedErrorMessage,
                    fieldError.getDefaultMessage());
        }

        return dto;
    }

    private String resolveLocalizedErrorMessage(FieldError fieldError) {
        Locale currentLocale = LocaleContextHolder.getLocale();
        String localizedErrorMessage = messageSource.getMessage(fieldError,
                currentLocale);

        // If a message was not found, return the most accurate field error code
        // instead.
        // You can remove this check if you prefer to get the default error
        // message.
        if (localizedErrorMessage.equals(fieldError.getDefaultMessage())) {
            String[] fieldErrorCodes = fieldError.getCodes();
            localizedErrorMessage = fieldErrorCodes[0];
        }

        return localizedErrorMessage;
    }
}

Validator

@Component
public class EmployeeValidator implements Validator {

    public boolean supports(Class clazz) {
        return Employee.class.isAssignableFrom(clazz);
    }

    public void validate(Object target, Errors errors) {
        ValidationUtils.rejectIfEmptyOrWhitespace(errors, "firstName", errors
                .getFieldError().getCode(), "First name is required.");
        ValidationUtils.rejectIfEmptyOrWhitespace(errors, "lastName", errors
                .getFieldError().getCode(),
                "Last name is required.");
        ValidationUtils.rejectIfEmptyOrWhitespace(errors, "email", errors
                .getFieldError().getCode(),
                "Email is required.");

    }

}

Model

@Entity
@Table(name = "employee")
@JsonIgnoreProperties({ "hibernateLazyInitializer", "handler" })
public class Employee implements Serializable {

    private static final long serialVersionUID = 1L;

    @Id
    @GeneratedValue
    @Column(name = "id")
    private long id;

    // @NotEmpty(message = "Please enter first name")
    @Column(name = "first_name")
    private String firstName;

    // @NotEmpty(message = "Please enter last name")
    @Column(name = "last_name")
    private String lastName;

    // @NotEmpty(message = "Please enter email address")
    @Email(message = "Please enter valid email address")
    @Column(name = "email", unique = true)
    private String email;

    @NotEmpty(message = "Please enter mobile number")
    @Size(min = 10, message = "Please enter valid mobile number")
    @Column(name = "phone")
    private String phone;

//Getter and Setter

}
like image 429
imbond Avatar asked Oct 20 '22 03:10

imbond


1 Answers

In your aproach you are using Server side validations but only in the controller layer. Have you tryied to use Bussines layer validations, like Hibernate Validation API http://hibernate.org/validator/

I've used it in a recent project and form me it's a great way to keep data consistent. Some tweaks and utils were needed to make it work as we wanted but it was not too difficult. For example, this validations, by default, are only checked just after persisting a Object in database, but in our controller we needed to make this validations earlier, so you we had to implement a way to call validation mechanism that relies on hibernate validation mechanism. Or, as another example, we had to develop a similar system on a web service to return errors when incoming data was not valid.

One way to use validations when needed is to implement it on all your bussines objects. They can inherit for a class like this:

import java.io.Serializable;
import java.util.ArrayList;
import java.util.HashMap;
import java.util.HashSet;
import java.util.List;
import java.util.Map;
import java.util.Set;

import javax.validation.ConstraintViolation;
import javax.validation.Validation;
import javax.validation.Validator;
import javax.xml.bind.annotation.XmlTransient;

import org.apache.commons.logging.Log;
import org.apache.commons.logging.LogFactory;

import com.fasterxml.jackson.annotation.JsonIgnore;

public abstract class BusinessObject implements Serializable, IObjectWithReport, IBusinessObject {

  private static Log log = LogFactory.getLog(BusinessObject.class.getName());

  private final Validator validator = Validation.buildDefaultValidatorFactory().getValidator();

  @JsonIgnore
  private Set<ConstraintViolation<BusinessObject>> errors;

  /* Validation methods */

  public final boolean valid() {
    preValidate();
    errors = new HashSet<ConstraintViolation<BusinessObject>>();
    errors = validator.validate(this);
    postValidate();
    return errors.isEmpty();
  }

  /**
   * Method to be overwriten in subclases so any BO can make some arrangement before checking valid
   */
  protected void preValidate() {
    log.trace("Generic prevalidate of " + this.getClass().getName());
  }
  /**
   * Method to be overwriten in subclases so any BO can make some arrangement once validation has been made
   */
  protected void postValidate() {
    log.trace("Generic postValidate of " + this.getClass().getName());
  }

  public Set<ConstraintViolation<BusinessObject>> getErrors() {
    return errors;
  }

  public boolean hasErrors() {
    return errors != null && !errors.isEmpty();
  }
}

Note that i use standard javax.validation.Validation API (check references here JPA 2.0 : what is javax.validation.* package?). But the implementation i use is the one from Hibernate.

Pros:

  1. Validations are placed in one single layer, not spread along various layers. So they are easier to maintain.
  2. Better model consistency because of that data is always validated in the same way, independently of how it was generated (user input, web service, pulled from other systems, etc).

Cons:

  1. You need to develop some utils so you can use Model Validations in other layers, but it's not very dificult.
  2. May be overkill if you have a simple project, whithout complexities like many info sources (user input, webservices input, rest services, other database systemas, etc) or interactions.
like image 120
Ricardo Vila Avatar answered Oct 21 '22 20:10

Ricardo Vila