Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Howto write custom checks/validation for the play-framework

I try to write checks for the play-framework and see two different possibilities. I described both and want to know if my understanding is correct (So it's more a tutorial than a question, specially because I didn't get any response that I missed something). So what possibilities exists.

  1. The simple way: Extending the class Check:
    Advantages: Easier to write, easier to read
    Disadvantages: You can't parametrized the check, you can only define the message.
  2. The advanced way: Writing an check based on OVal AbstractAnnotationCheck.
    Advantages: You can parametrized the check and have a simpler to use annotation
    Disadvantages: A little bit more complicated.

Before we have a look on the implementation I want to explain the messages. You can always set the message directly or use a key to refer the message in a message-properties. The last one is the cleaner and recommended way. Every validation get a least 1 parameter: The name of the property which isn't valid. So validation or check specific parameters are always referred with %i$s where i>1. The format of the message string should follows the rules of Formatter but I'm unsure if all features are supported. As far as I know only %s, %d and %f is supported togeter with positioning. So %[argument_index$][flags]conversion where conversion could only be s,d or f.

Lets have a look on two examples: The simple way I used in my module for optimistic locking:

/**
 * Check with proof if the version of the current edited object is lesser
 * than the version in db.
 * Messagecode: optimisticLocking.modelHasChanged
 * Parameter: 1 the request URL.
 * Example-Message: The object was changed. <a href="%2$s">Reload</a> and do your changes again.
 *
 */
static class OptimisticLockingCheck extends Check {

    /**
     * {@inheritDoc}
     */
    @Override
    public boolean isSatisfied(Object model, Object optimisiticLockingViolatedValue) {
        //The comparision of version was made in the setter. Here
        //we only have to check the flag.
        if (((VersionedModel) model).optimisiticLockingViolated) {
            final Request request = Request.current();
            //The following doesn't work in 1.0 but in 1.1 see https://bugs.launchpad.net/play/+bug/634719
            //http://play.lighthouseapp.com/projects/57987-play-framework/tickets/116
            //setMessage(checkWithCheck.getMessage(), request != null ? request.url : "");
            setMessage("optimisticLocking.modelHasChanged", request != null ? request.url : ""); 

        }
        return !((VersionedModel) model).optimisiticLockingViolated;
    }
}

You use this Check with the annotation @CheckWith(value=OptimisticLockingCheck.class, message="optimisticLocking.modelHasChanged")

So lets have a closer look how it works. The only thing we have to do is to extends the class play.data.validation.Check and overwrite the isSatisfied method. There you get your model and the value of the properties. All you have to do is to return true if everything is OK or false otherwise. In our case we want to set the current url as a parameter. This can be easily done by calling setMessage(). We give the message or the message key which is defined in the messages properties and the parameters. Remember we only give 1 parameter but referred as with %2$s, because the first parameter is always the name of the property.

Now the complex way based on the Range-check of play: First we need to define an Annotation

/**
 * This field must be lower than and greater than.
 * Message key: validation.range
 * $1: field name
 * $2: min reference value
 * $3: max reference value
 */
@Retention(RetentionPolicy.RUNTIME)
@Target({ElementType.FIELD, ElementType.PARAMETER})
@Constraint(checkWith = RangeCheck.class)
public @interface Range {

    String message() default RangeCheck.mes;
    double min() default Double.MIN_VALUE;
    double max() default Double.MAX_VALUE;
}

and then the Check

@SuppressWarnings("serial")
public class RangeCheck extends AbstractAnnotationCheck<Range> {

    final static String mes = "validation.range";

    double min;
    double max;

    @Override
    public void configure(Range range) {
        this.min = range.min();
        this.max = range.max();
        setMessage(range.message());
    }

    public boolean isSatisfied(Object validatedObject, Object value, OValContext context, Validator validator) {
        requireMessageVariablesRecreation();
        if (value == null) {
            return true;
        }
        if (value instanceof String) {
            try {
                double v = Double.parseDouble(value.toString());
                return v >= min && v <= max;
            } catch (Exception e) {
                return false;
            }
        }
        if (value instanceof Number) {
            try {
                return ((Number) value).doubleValue() >= min && ((Number) value).doubleValue() <= max;
            } catch (Exception e) {
                return false;
            }
        }
        return false;
    }

    @Override
    public Map<String, String> createMessageVariables() {
        Map<String, String> messageVariables = new TreeMap<String, String>();
        messageVariables.put("2-min", Double.toString(min));
        messageVariables.put("3-max", Double.toString(max));
        return messageVariables;
    }

}

OK I think the annotation don't must be explained. Lets have look on the check. In this case it's extends net.sf.oval.configuration.annotation.AbstractAnnotationCheck. We have to write a configure-method where we get the annotation and can copy the parameters. Then we have to define our check. Which is analog to the implementation of the other check. So we only write our condition and return true or false, except one special line! If we used a parametrized message, we must call requireMessageVariablesRecreation(); in our method. At least we must override the method createMessageVariables. Here we have to get a littlebit play-knowlegde (all the other stuff is described here). You put your messages into an map with a key and value, but play only takes the values (see ValidCheck.java in framework code). So it will be referenced by position. This is the reason I changed the implementation of the RangeCheck using TreeMap instead of HashMap. Furthermore I let the keys start with the index which they can referred.

So I hope this makes it more clear how to write custom validations/checks for play. I hope the description is correct. Therefor the question is my understanding correct?

like image 736
niels Avatar asked Sep 11 '10 09:09

niels


People also ask

Why validation is important?

Validation means that you understand where the other person is coming from, even if you disagree with what they say or do (Rather & Miller, 2015). Recognizing that someone's feelings and thoughts make sense can show that we are listening nonjudgmentally and can help build stronger relationships, especially in therapy.

What is the need of validation framework?

The validation framework uses Spring's powerful expression evaluation engine to evaluate both validation rules and applicability conditions for the validator. As such, any valid Spring expression can be specified within the test and when attributes of any validator.


1 Answers

At least your first example appears to be on the correct path. You can compare it to the documentation provided below, but I'd assume from the complexity of your example that you've already referred to it.

http://www.playframework.org/documentation/1.1/validation#custom

I don't know enough about the play framework to comment on the second example.

like image 51
Thomas Langston Avatar answered Oct 06 '22 01:10

Thomas Langston