13

Consider the following pojo for reference:

public class User{

    private  String username;
    private String firstName;
    private String middleName;
    private String lastName;
    private String phone;

    //getters and setters

}

My application is a basically spring-boot based REST API which exposes two endpoints, one to create the user and the other to retrieve a user.

The "users" fall into certain categories, group-a, group-b etc. which I get from the headers of the post request.

I need to validated the user data in runtime and the validations may differ based on the group of a user.

for example, the users that fall into group-a may have phone numbers as an optional field whereas it might be a mandatory field for some other group.

The regex may also vary based on their groups.

I need to be able to configure spring, to somehow dynamically validate my pojo as soon as they are created and their respective set of validations get triggered based on their groups.

Maybe I can create a yml/xml configuration which would allow me to enable this?

I would prefer to not annotate my private String phone with @NotNull and @Pattern.

My configuration is as follows:

public class NotNullValidator implements Validator {
    private String group;
    private Object target;

    public String getGroup() {
        return group;
    }

    public void setGroup(String group) {
        this.group = group;
    }

    public Object getTarget() {
        return target;
    }

    public void setTarget(Object target) {
        this.target = target;
    }

    @Override
    public void validate(Object o) {
        if (Objects.nonNull(o)) {
            throw new RuntimeException("Target is null");
        }
    }
}


public interface Validator {
    void validate(Object o);
}


@ConfigurationProperties(prefix = "not-null")
@Component
public class NotNullValidators {
    List<NotNullValidator> validators;

    public List<NotNullValidator> getValidators() {
        return validators;
    }

    public void setValidators(List<NotNullValidator> validators) {
        this.validators = validators;
    }
}


application.yml

not-null:
  validators:

    -
      group: group-a
      target: user.username

    -
      group: group-b
      target: user.phone

I want to configure my application to somehow allow the validators to pick their targets (the actual objects, not the strings mentioned in the yml), and invoke their respective public void validate(Object o) on their targets.

P.S.

Please feel free to edit the question to make it better.

I am using jackson for serializing and deserializing JSON.

Adit A. Pillai
  • 647
  • 1
  • 10
  • 22
  • 1
    Can you share more info about your validation method and where you put it ? – NNguyen Oct 05 '17 at 18:47
  • let's take a validator bean called notNullValidator, which checks if the phone number is null or not for group-b, if it is null, it'll throw some kind of exception, but for group-a it won't throw any exceptions. I understand that I might need to configure 2 different beans for this, that is okay, what I want is, this particular validation must be triggerd as soon as "user.phone" is set. – Adit A. Pillai Oct 06 '17 at 08:09
  • Code structure should be similar to what you have mentioned? Did you try with custom validator? – imk Oct 08 '17 at 16:07
  • It will be preferable if the code structure didn’t change. Can you suggest how do O go about implementing my custom validator to achieve my goal? – Adit A. Pillai Oct 08 '17 at 16:10

3 Answers3

17

The easiest solution to your problem, as i see it, is not with Spring or the POJOs themselves but with a design pattern.

The problem you're describing is easily solved by a strategy pattern solution.

You match the strategy to use by the header you're expecting in the request, that describes the type of user, and then you perform said validations inside the strategy itself.

This will allow you to use the same POJO for the whole approach, and deal with the specifics of handling/parsing and validating data according to the each type of user's strategy.

Here's a link from wiki books with a detailed explanation of the pattern

Strategy Pattern

Suppose you have a basic interface for your strategies:

interface Strategy { 

    boolean validate(User user);
}

And you have 2 different implementations for the 2 different types of user:

public class StrategyA implements Strategy {

    public boolean validate(User user){

         return user.getUsername().isEmpty();
    }
}

public class StrategyB implements Strategy {

    public boolean validate(User user){

         return user.getPhone().isEmpty();
    }
}

You add a Strategy attribute to your User POJO and assign the right implementation of the Strategy to that attribute when you receive the post request.

Everytime you need to validate data for that user you just have to invoke the validate method of the assigned strategy.

If each User can fit multiple strategies, you can add a List<Strategy> as an attribute instead of a single one.

If you don't want to change the POJO you have to check which is the correct strategy every time you receive a post request.

Besides the validate method you can add methods to handle data, specific to each strategy.

Hope this helps.

ricol070
  • 492
  • 2
  • 11
  • Sounds fine, if you could provide a sample implementation or a url for an example, it’ll be a lot more helpful – Adit A. Pillai Oct 09 '17 at 13:05
  • Added a link to the post, hope it helps. Comment again if you need further help! – ricol070 Oct 09 '17 at 13:20
  • I read about the mentioned Strategy pattern, and this is what I was looking for, but what I am not able to figure out is how to bind my validators to my POJO without having to edit my POJO. Having a strategy, is only half the solution. – Adit A. Pillai Oct 09 '17 at 13:48
  • You're seeing this with the wrong lenses i think. You don't need to bind the validators to your POJO, you implement the validation and the handling of the data in the strategies(and throwing/handling the exceptions as you want to), and leave the POJO as is. The strategies are the validators – ricol070 Oct 09 '17 at 14:04
  • I think I have misunderstood the concept, could you please provide a reference to an implementation which could clear this up with me? Also, do you mean that I validate the JSON that is coming into my api? – Adit A. Pillai Oct 09 '17 at 14:06
  • Added a simple example of how you could implement it. Hope it helps! – ricol070 Oct 09 '17 at 14:16
  • yes, it was a big help towards achieving the solution. – Adit A. Pillai Oct 11 '17 at 18:04
  • Great to hear! If you can mark it as answered, i'd be grateful! – ricol070 Oct 11 '17 at 18:18
6

You can use validation groups to control which type of user which field gets validated for. For example:

@NotBlank(groups = {GroupB.class})
private String phone;

@NotBlank(groups = {GroupA.class, GroupB.class})
private String username;

Then you use the headers from the request that you mentioned to decide which group to validate against.

See http://blog.codeleak.pl/2014/08/validation-groups-in-spring-mvc.html?m=1 for a complete example.


Updated to include a more comprehensive example:

public class Val {
    private Validator validator = Validation.buildDefaultValidatorFactory().getValidator();

    public boolean isValid(User user, String userType) {
        usergroups userGroup = usergroups.valueOf(userType);
        Set<ConstraintViolation<User>> constraintViolations = validator.validate(user, userGroup.getValidationClass());
        return constraintViolations.isEmpty();
    }

    public interface GroupA {}
    public interface GroupB {}

    public enum usergroups {
        a(GroupA.class),
        b(GroupB.class);

        private final Class clazz;

        usergroups(Class clazz) {
            this.clazz = clazz;
        }

        public Class getValidationClass() {
            return clazz;
        }
    }
}

This doesn't use application.yaml, instead the mapping of which fields are validated for each group is set in annotations, similar results using Spring's built in validation support.

SergeyB
  • 9,478
  • 4
  • 33
  • 47
  • That would mean that I’ll have to create GroupA and GroupB marker interfaces for my validations right? I’m trying to avoid that as well – Adit A. Pillai Oct 07 '17 at 19:07
  • I’ll try this out and update the question with concerns, if any. – Adit A. Pillai Oct 07 '17 at 19:20
  • Though it seems to work, it still requires me to annotate my POJO class with validation groups, say I introduce a new group, then I'll have to modify all my POJOs, which might then require me to test them again. This is the reason why I am not very comfortable in touching my POJOs. – Adit A. Pillai Oct 08 '17 at 07:17
  • Adding a new group to a yaml file would require an equal amount of testing as well. – SergeyB Oct 08 '17 at 13:02
  • Agreed, but at least that way I wouldn’t have to touch my POJO class – Adit A. Pillai Oct 08 '17 at 13:03
1

I was able to solve my problem with the use of Jayway JsonPath. My solution goes as follows:

  1. Add a filter to your API which has the capability to cache the InputStream of the ServletRequest since it can be read only once. To achieve this, follow this link.
  2. Create a bunch of validators and configure them in your application.yml file with the help of @ConfigurationProperties. To achieve this, follow this link
  3. Create a wrapper which would contain all your validators as a list and initialize it with @ConfigurationProperties and the following configuration:

    validators:
      regexValidators:
        -
          target: $.userProfile.lastName
          pattern: '[A-Za-z]{0,12}'
          group: group-b
    
      minMaxValidators:
        -
          target: $.userProfile.age
          min: 18
          max: 50
          group: group-b
    
  4. Call the validate method in this wrapper with the group which comes in the header, and then call the validate of the individual validators. To achieve this, I wrote the following piece of code in my wrapper:

    public void validate(String input, String group) {
        regexValidators.stream()
                .filter(validator -> group.equals(validator.getGroup()))
                .forEach(validator -> validator.validate(input));
    
        minMaxValidators.stream()
                .filter(validator -> group.equals(validator.getGroup()))
                .forEach(validator -> validator.validate(input));
    }
    

and the following method in my validator:

public void validate(String input) {
    String data = JsonPath.parse(input).read(target);
    if (data == null) {
        throw new ValidationException("Target:  " + target + "  is NULL");
    }
    Matcher matcher = rule.matcher(data);
    if (!matcher.matches()) {
        throw new ValidationException("Target:  " + target + "  does not match the pattern:  " + pattern);
    }
}

I have created a functioning project to demonstrate the validations and it can be found here.
I understand that the answer alone might not be very clear, please follow the above mentioned url for the complete source code.

Adit A. Pillai
  • 647
  • 1
  • 10
  • 22