All Downloads are FREE. Search and download functionalities are using the official Maven repository.

templates.docs.validation.html Maven / Gradle / Ivy

There is a newer version: 2.2.0
Show newest version
{#==========================================
Docs : "Validation"
==========================================#}

Validation

Spincast provides a good set of utilities to validate objects. We decided to base those utilities on plain Java code and not on annotations. Validation annotations may seem convenient at first, but, in a real project, you quickly realize that you can't express all the details you need : you can't use complex logic, you can't validate an element based on the result of the validation of another element, etc. In general, you end up writing plain Java code anyway in addition to those annotations... But then your validation logic is scattered in many places and this is not the ideal.

In Spincast, you validate an object using three main steps :

  1. You create a Validation Set instance. This is a container where the results of the validation will be saved.
  2. You use Predefined Validations or custom validation code to actually validate the object. Those validations may result in Validation Messages. Most of the time those are Error Validation Messages resulting from failed validations ("This email is invalid", for example).

  3. When the validation is all done, you use the resulting Validation Messages by retrieving them from the Validation Set. In general, you want to display them to a user, or you generate some kind of report.

Here's a quick example :

// Creation of a Validation Set
ValidationSet myValidationSet = getValidationFactory().createValidationSet();

// The object we're going to validate
User user = getUser();

// Validates the email of the user using the 
// "validationEmail()" predefined validation
myValidationSet.validationEmail()
               .key("email")
               .element(user.getEmail())
               .validate();
                                           
// Validates the username of the user, using
// custom validation code
String username = user.getUsername();
if(username == null || username.length() < 3 || username.length() > 42) {
    myValidationSet.addError("username",
                             "USERNAME_INVALID",
                             "Must contain between 3 and 42 characters.");  
}

// Uses/Displays the resulting Validation Messages
if(!myValidationSet.isValid()) {
    
    Map<String, List<ValidationMessage>> validationMessages = myValidationSet.getMessages();
    
    // ... do something with the messages
}

In the JsonObject validation section, we'll see that validation is even easier when done using JsonObjects...

The Validation Set

At the core of a validation process, is a ValidationSet. A Validation Set is a set of Validation Messages and of utility methods. It acts as a container to save the results of the various validations run on an object.

For example, let's say we validate a User object. At the end of this validation, the resulting Validation Set may contain two Validation Messages :

  • An Error Validation Message : "The email is invalid."
  • A Success Validation Message : "This username is available!"

There are two ways of creating a Validation Set instance :

  • By injecting and using the ValidationFactory :

    ValidationFactory validationFactory = getValidationFactory();
    ValidationSet validationSet = validationFactory.createValidationSet();

  • By using the validationSet(...) method on a JsonObject :

    JsonObject responseModel = context.response().getModel();
    JsonObjectValidationSet validationSet = responseModel.validationSet();

    When you get a Validation Set this way, it is bound to the JsonObject it originates from. A JsonObjectValidationSet is returned instead of a plain Validation Set, and you have access to extra methods dedicated to validate the associated JsonObject. We'll see that in details in the JsonObject validation section.

A Validation Set is more than a simple container to store the validation results. It also provides Predefined Validations and a bunch of other utility methods. Those utility methods are :

  • ValidationSet addMessage(String validationKey, ValidationMessage validationMessage)
    Adds a new Validation Message to this set, using the specified validation key.
    @return itself (fluent style)
  • ValidationSet addMessage(String validationKey, ValidationLevel messageLevel, String code, String text)
    Creates and adds a new Validation Message.
    @return itself (fluent style)
  • ValidationSet addError(String validationKey, String code, String text)
    Creates and adds a new Error Validation Message.
    @return itself (fluent style)
  • ValidationSet addWarning(String validationKey, String code, String text)
    Creates and adds a new Warning Validation Message.
    @return itself (fluent style)
  • ValidationSet addSuccess(String validationKey, String code, String text)
    Creates and adds a new Success Validation Message.
    @return itself (fluent style)
  • ValidationSet mergeValidationSet(ValidationSet set)
    Merges an existing Validation Set.
    @return itself (fluent style)
  • ValidationSet mergeValidationSet(String validationKeyPrefix, ValidationSet result)

    Merges an existing Validation Set but prefixes all the validation keys with the specified validationKeyPrefix.

    The validationKeyPrefix parameter allows you to merge a Validation Set resulting from a validation not performed directly on your current Validation Set (you may be reusing an external Validator for example) and still have the keys of the merged Validation Messages to properly represent JsonPaths on your current Set. We'll talk about that in a next section...

    @return itself (fluent style)
  • boolean hasMessages()
    Does this Validation Set contain any Validation Messages?
  • boolean hasMessages(String validationKey)
    Are there some Validation Messages associated with the specified validation key?
  • boolean isWarning()
    Returns true if the Validation Set contains at least one Warning Validation Message, but no Error Validation Messages.
  • boolean isWarning(String validationKey)
    Returns true if there is at least one Warning Validation Message, but no Error Validation Messages associated with the specified validation key.
  • boolean isSuccess()
    Returns true if the validation set only contains Success Validation Messages (or contains no messages at all).
  • boolean isSuccess(String validationKey)
    Returns true if there are only Success Validation Messagesassociated with the specified validation key (or contains no messages at all).
  • boolean isError()
    Returns true if the validation set contains at least one Error Validation Message.
  • boolean isError(String validationKey)
    Returns true if there is at least one Error Validation Message associated with the specified validation key.
  • boolean isValid()

    Returns true if the Validation Set does not contain any Error Validation Messages. The set can contain Warning Validation Message and Success Validation Message.

    This is a synonym of !isError().

  • boolean isValid(String validationKey)

    Returns true if there are no Error Validation Messages associated with the specified validation key. There can be Warning Validation Message and Success Validation Message.

    This is a synonym of !isError(validationKey).

  • Map<String, List<ValidationMessage>> getMessages()

    Gets the Validation Messages, with their associated validation keys as the leys of the Map.

    The Map and the List values are immutable.

  • List<ValidationMessage> getMessages(String validationKey)

    Gets the Validation Messages associated with the specified validation key.

    The Map and the List values are immutable.

  • String getMessagesFormatted(ValidationMessageFormatType formatType)
    Quick way to get a formatted version of the Validation Messages.

    @param formatType The type of output for the messages (Text, HTML, Json or XML).
    @return the formatted messages or null if there are no validation messages.
  • String getMessagesFormatted(String validationKey, ValidationMessageFormatType formatType)
    Quick way to get a formatted version of the Validation Messages associated with the specified validation key.

    @param formatType The type of output for the messages (Text, HTML, Json or XML).
    @return the formatted messages or null if there are no validation messages.
  • public JsonObject convertToJsonObject()

    Converts the Validation Set to a JsonObject object. Read the Converting to a JsonObject section to see what the resulting JsonObject object looks like!

    The resulting JsonObject object is immutable.

Validation Messages

A ValidationMessage is composed of three things :

  • A Validation Level. This level can be :
    • Error
    • Warning
    • Success
  • A text, which is the description of the validation result. For example : "This email is invalid".
  • A code, which in general is a constant representing the type of validation this Validation Message is associated with. For example : "VALIDATION_TYPE_EMAIL".

When you add a Validation Message to a Validation Set, you do so using a validation key. The validation key allows you to retrieve the Validation Messages associated with a specific element from the Validation Set.

Here's a quick example where we perform a validation and create a Validation Message :


ValidationSet validatorSet = validationFactory.createValidationSet();
JsonObject user = getUser();
        
String thirdBookTitle = user.getString("user.books[2].title");
if(containsNsfwWords(thirdBookTitle)) {
    
    ValidationMessage message = 
            getValidationFactory().createMessage(ValidationLevel.WARNING, 
                                                 "NSFW_MATERIAL", 
                                                 "This book contains NSFW material.");
                                                 
    validatorSet.addMessage("user.books[2].title", message);
}

Explanation :

  • 2 : We create a Validation Set.
  • 3 : The user (as a JsonObject) that we're going to validate.
  • 5 : We get the title of the third book of the user. We use a JsonPath to target it!
  • 6 : We perform a manual validation on that title. We could use a Predefined Validation too.
  • 8 : We create a Validation Message to represent the validation result.
  • 9 : The level of this Validation Message. Here, it's going to be a Warning Validation Message. The two other possible levels are Error and Success.
  • 10 : The code (or "type") of the validation the Validation Message is associated with.
  • 11 : The text for the Validation Message.
  • 13 : We add the Validation Message to the Validation Set using the "user.books[2].title" validation key. We'll see in the next section that using the JsonPath of the validated element as its validation key is often a good idea!

Validation Sets also provides some methods to quickly add Validation Messages, without having to create the messages manually. For example :

// Creates and adds an Error Validation Message
validatorSet.addError("user.books[2].title", "LENGTH", "The title is too long");

// Creates and adds a Warning Validation Message
validatorSet.addWarning("user.books[2].title", "LENGTH", "The title is rather long");

// Creates and adds a Success Validation Message
validatorSet.addSuccess("user.books[2].title", "LENGTH", "The title length is perfect");

Validation Keys

A validation key is used to associate Validation Messages to the validated element they have been created for. You can think of a Validation Set as a big Map<String, List<ValidationMessage>> where the keys are validation keys and where the values are the Validation Messages.

If you validate a simple email element for example, you could use "email" or "email-validation" as the validation key. It's up to you to choose a meaningful name for the key : you will later use it to retrieve all the Validation Messages associated with this element so you can display them, create a report from them, etc.

Even if you can use any string as a validation key, we suggest that you stick to some conventions since a validation key must be unique and should well represent the element it is associated with.

The convention we suggest is that, when possible, you use the JsonPath of the validated element as the validation key.

Let's say we validate this user object :

{
    "name": "Stromgol",
    "email": "[email protected]"
    "books": [
        {
            "title" : "Dune",
            "author": "Frank Herbert"
        },
        {
            "title" : "The Hitchhiker's Guide to the Galaxy",
            "author" : "Douglas Adams"
        }
    ]
}

First, we will validate the email :

JsonObject user = getUser();

// We create a Validation Set
ValidationSet validationSet = getValidationFactory().createValidationSet();

if(!isEmailValid(user.getString("email"))) {

    // We add a Validation Message using "email" as the validation key
    validationSet.addError("email", "EMAIL_VALIDATION", "Invalid email");  
}

We can later retrieve the Validation Messages associated with the email element using that "email" validation key :


List<ValidationMessage> validationMessages = validationSet.getMessages("email");

Very straightforward! But now let's try to do the thing with the title of the first book :

// Gets the title of the first book, using its JsonPath
String title = user.getString("books[0].title");

if(!isTitleValid(title) {

    // We add a Validation Message using "title" as the validation key...
    // This may not be a good idea!
    validationSet.addError("title", "TITLE_VALIDATION", "Invalid title");  
}
 
List<ValidationMessage> validationMessages = validationSet.getMessages("title");

We may be tempted to use a very short name as the validation key, "title" for example. It would work, but it's not a very good idea... The problem is that now the validation key doesn't well represent the validated element it is associated with. Remember that each validation key must be unique. What happens if we also want to validate the title of the second book? Or if there are more than one user with books to validate? What validation keys will we use then? We can't use the same "title" validation key for more than one element!

Since each element of a JsonObject already has a unique identifier, which is its JsonPath, why not use this identifier as its validation key?

// Gets the title of the first book, using its JsonPath
String title = user.getString("books[0].title");

if(!isTitleValid(title) {

    // We add a Validation Message using the JsonPath
    // of the validated element as the validation key : 
    // much better!
    validationSet.addError("books[0].title", "TITLE_VALIDATION", "Invalid title");  
}
 
List<ValidationMessage> validationMessages = validationSet.getMessages("books[0].title");

Now there won't be any conflict, even if there are a lot of validation keys in our Validation Set, and every key will be clearly indicate which validated element it is associated with!

Note that there are situations where you can't use a JsonPath for a validation key. You don't always validate elements individually, for instance. Sometimes, a combination of elements is invalid, and therefore no JsonPath is available.

In those situations, we suggest that you scope the validation key as much as possible, and that you use creativity in order to make the key unique and meaningful!

For example, let's say that an HTML form has been submitted and you need to validate that two passwords from it match. If they don't match, you could had an Error Validation Message for both of those elements, or only for the second one (you could say that only the second one is invalid since it doesn't match the first one). But you could also want to add a Validation Message to represent that invalid combination, not the fields taken individually. You may want to display a "The passwords don't match" error in the section where both fields are, for example.

In such situations, you can't use a JsonPath as the validation key, since you are not referring to a single element. But we suggest that you still try to scope the validation key, so it is as unique and meaningful as possible. For example, that key could be "myForm.user.passwordsMatch" :

ValidationSet validationSet = getValidationFactory().createValidationSet();

JsonObject formData = context.request().getFormData();

// Gets the passwords using their JsonPath
String password1 = formData.getString("myForm.user.password1", "");
String password2 = formData.getString("myForm.user.password2", "");
if(!password1.equals(password2)) {

    // Use a scoped and meaningful
    // validation key, even if it's not 
    // a true JsonPath!
    validationSet.addError("myForm.user.passwordsMatch", 
                           "PASSWORDS_MATCH", 
                           "The password don't match!");  

A question remains : what happens to this "Use the JsonPath of a validated element as its validation key" convention when we validate an element using an external Validator? For example, we may have a TitleValidator that can be reused in multiple places in our application. For example :

ValidationSet validationSet = getValidationFactory().createValidationSet();

String title = getUser().getString("books[0].title");

TitleValidator titleValidator = getTitleValidator();

ValidationSet titleValidationSet = titleValidator.validate(title);

validationSet.mergeValidationSet(titleValidationSet);

// Oups! 
// The validation key is "title" here, not "books[0].title" as we
// would like...
List<ValidationMessage> messages = validationSet.getMessages("title");

External validators return a Validation Set and you merge this set to your own local set [9] (you can learn more about merging Validation Sets in the Sharing / Merging Validation Sets section).

The problem here is that TitleValidator has no idea of the position of the "title" element inside our local user object! It will see the "title" element as being a root element and therefore it won't generate a validation key respecting our local JsonPath... It will probably simply use "title" as the validation key.

For this reason, the mergeValidationSet(...) method accepts a "validationKeyPrefix" parameter. If this parameter is specified, all the validation keys will be prefixed with it when merged :

ValidationSet validationSet = getValidationFactory().createValidationSet();

String title = getUser().getString("books[0].title");

TitleValidator titleValidator = getTitleValidator();

// We use the external validator
ValidationSet titleValidationSet = titleValidator.validate(title);

// We prefixe the merged validation keys
validationSet.mergeValidationSet("books[0].", titleValidationSet);

// The Validation Messages can now be retrieved using
// JsonPaths thast are valid on our validated root object!
List<ValidationMessage> messages = validationSet.getMessages("books[0].title");

There is also a prefixValidationKeys(...) method which can be used to prefix the validation keys of a Validation Set directly, without having to merge this set into another one. This can be useful, for example if you want to prefix the validation keys before adding a Validation Set to a JsonObject :

// Uses an external validator
ValidationSet validationSet = getExternalValidator().validate(someObject);
        
// Prefixes the resulting validation keys
// *directly on the Validation Set*
validationSet.prefixValidationKeys("someObject.");

// Adds the Validation Set (with the validation keys
// now modified) to the response model
context.response().getModel().set("validation", validationSet);

Predefined Validations

Spincast provides some predefined validations to help validate an object. They are defined on the ValidationSet interface so there are easily accessible during a validation process. Let's have a look at an example :

// We create an inital Validation Set
ValidationSet myValidatorSet = getValidationFactory().createValidationSet();

// This is a username we're going to validate!
String username = getSomeUsername();

// Validates that the username is not blank and, only if 
// it's not, validates that it contains at least 3 characters.
ValidationSet lastResult = myValidatorSet.validationNotBlank()
                                         .key("username")
                                         .element(username)
                                         .validate();
if(lastResult.isValid()) {
    lastResult = myValidatorSet.validationMinLength(3)
                               .key("username")
                               .element(username)
                               .validate();
}

Notice that a predefined validation returns it's own Validation Set [9]! This allows you to conditionally apply a validation depending on the result of a previous one [13].

To use a predefined validation :

  • You call the associated validateXXXXXX(...) method on your Validation Set. For example : myValidatorSet.validationNotBlank(). This starts a builder to create the validation.
  • You specify a validation key using the "key(...)" method on the builder. This key associate the resulting Validation Messages to the validated elements. For example : .key("username").
  • You specify the element to validate. For example : .element(username).
  • You finally call .validate() to perform the actual validation.

When the validate() method is called, the validation is perform and :

  • The resulting Validation Messages are automatically added to the root Validation Set.
  • A new Validation Set, representing this particular validation only, is returned. This allows you to conditionally apply a validation depending on the result of a previous one.

Here's the list the predefined validations available on a Validation Set :

  • ValidationBuilderKey validationNotBlank()
    Starts the creation of a "not blank" validation (null, empty or spaces only).
  • ValidationBuilderKey validationBlank()
    Starts the creation of a "must be blank" validation.
  • ValidationBuilderKey validationEmail()
    Starts the creation of an email validation.
  • ValidationBuilderKey validationNotNull()
    Starts the creation of a "not null" validation.
  • ValidationBuilderKey validationNull()
    Starts the creation of a "must be null" validation.
  • ValidationBuilderKey validationPattern(String pattern)
    Starts the creation of a "pattern must match" validation.
  • ValidationBuilderKey validationNotPattern(String pattern)
    Starts the creation of a "pattern must not match" validation.
  • ValidationBuilderKey validationSize(int size, boolean ignoreNullValues)
    Starts the creation of a "size" validation.
    @param ignoreNullValues If true, null values will be ignored in the total count. For example, if the validation is run on a JsonArray which is [null, null "abc"], then the size would be : "3" if ignoreNullValues is false, "1" if it's true.
  • ValidationBuilderKey validationMinSize(int minSize, boolean ignoreNullValues)
    Starts the creation of a "minimum size" validation.
    @param ignoreNullValues If true, null values will be ignored in the total count. For example, if the validation is run on a JsonArray which is [null, null "abc"], then the size would be : "3" if ignoreNullValues is false, "1" if it's true.
  • ValidationBuilderKey validationMaxSize(int maxSize, boolean ignoreNullValues)
    Starts the creation of a "maximum size" validation.
    @param ignoreNullValues If true, null values will be ignored in the total count. For example, if the validation is run on a JsonArray which is [null, null "abc"], then the size would be : "3" if ignoreNullValues is false, "1" if it's true.
  • ValidationBuilderKey validationLength(int length)
    Starts the creation of a "length" validation.
  • ValidationBuilderKey validationMinLength(int minLength)
    Starts the creation of a "minimum length" validation.

    A null value makes this validation fail, otherwise toString() is called on the object to check its "length".

  • ValidationBuilderKey validationMaxLength(int maxLength)
    Starts the creation of a "maximum length" validation.

    A null value is a success, otherwise toString() is called on the object to check its "length".

  • ValidationBuilderKey validationEquivalent(Object reference)

    Starts the creation of a "equivalent" validation.

    To validate if an element is equivalent to another, one is converted to the type of the other (when this is required). If this conversion fails, the elements are not equivalent. When both elements are of the same type, they are compared using their equals(...) method. This process is done in the ObjectConverter#isEquivalent(...) method.

  • ValidationBuilderKey validationNotEquivalent(Object reference)

    Starts the creation of a "not equivalent" validation.

    To validate if an element is equivalent to another, one is converted to the type of the other (when this is required). If this conversion fails, the elements are not equivalent. When both elements are of the same type, they are compared using their equals(...) method. This process is done in the ObjectConverter#isEquivalent(...) method.

  • ValidationBuilderKey validationLess(Object reference) throws CantCompareException

    Starts the creation of a "less than" validation.

    To validate if an element is less than another, one is converted to the type of the other (when this is required). If this conversion fails, the elements can't be compared. The resulting type of the elements must implement the Comparable interface, so the compareTo(...) method is used to compare the two elements.

    An null element is always less than a non-null element.

    @throws CantCompareException if the two elements can't be compared together.
  • ValidationBuilderKey validationGreater(Object reference) throws CantCompareException

    Starts the creation of a "greater than" validation.

    To validate if an element is greater than another, one is converted to the type of the other (when this is required). If this conversion fails, the elements can't be compared. The resulting type of the elements must implement the Comparable interface, so the compareTo(...) method is used to compare the two elements.

    An null element is always less than a non-null element.

    @throws CantCompareException if the two elements can't be compared together.
  • ValidationBuilderKey validationEquivalentOrLess(Object reference) throws CantCompareException

    Starts the creation of a "equivalent or less" validation.

    To validate if an element is equivalent or less than another, one is converted to the type of the other (when this is required). If this conversion fails, the elements can't be compared. The resulting type of the elements must implement the Comparable interface, so the compareTo(...) method is used to compare the two elements.

    An null element is always less than a non-null element.

    @throws CantCompareException if the two elements can't be compared together.
  • ValidationBuilderKey validationEquivalentOrGreater(Object reference) throws CantCompareException

    Starts the creation of a "equivalent or greater" validation.

    To validate if an element is equivalent or greater than another, one is converted to the type of the other (when this is required). If this conversion fails, the elements can't be compared. The resulting type of the elements must implement the Comparable interface, so the compareTo(...) method is used to compare the two elements.

    An null element is always less than a non-null element.

    @throws CantCompareException if the two elements can't be compared together.

Predefine Validation options

Some options are available during the process of using a predefine validation :

  • Instead of validating a single element, using the "element(...)" method of the builder, you can validate a JsonArray of elements by using the "all(...)" method. Doing so, all the elements of the array will be validated and some Validation Messages will potentially be added for each of them!

    Note that, when such array is validated, the generated validation keys are going to be the key of the array + the position of the elements in the array. For example :

    ValidationSet myValidatorSet = validationFactory.createValidationSet();
    
    JsonArray titles = getJsonManager().createArray();
    titles.add("A valid title");
    titles.add("");
    titles.add("  ");
    titles.add(null);
    
    // Using ".all(titles)", this validates all the elements
    // of the array
    ValidationSet lastResult = myValidatorSet.validationNotBlank()
                                             .key("titles")
                                             .all(titles)
                                             .validate();

    In this example, the resulting Validation Set will contain three Error Validation Messages : one with the key "titles[1]", one with the key "titles[2]", and one with the key "titles[3]". The first title ("titles[0]") is valid so no Validation Message will be added for it.

  • By default a Validation Message is added only when the validation fails. If you want to add a Success Validation Message, when the validation is sucessful, you can use the "addMessageOnSuccess(...)" method. For example :

    ValidationSet myValidatorSet = validationFactory.createValidationSet();
    
    JsonArray titles = getJsonManager().createArray();
    titles.add("A valid title");
    titles.add("");
    
    ValidationSet lastResult = myValidatorSet.validationNotBlank()
                                             .key("titles")
                                             .all(titles)
                                             .addMessageOnSuccess()
                                             .validate();

    In this example, the resulting Validation Set will contain two Validation Messages : a Success Validation Message with the key "titles[0]" and an Error Validation Message with the key "titles[1]".
  • When using the "addMessageOnSuccess(...)" method, you can specify the text that is going to be used for the message (instead of the default one). For example :

    ValidationSet myValidatorSet = validationFactory.createValidationSet();
    
    JsonArray titles = getJsonManager().createArray();
    titles.add("A valid title");
    titles.add("");
    
    ValidationSet lastResult = myValidatorSet.validationNotBlank()
                                             .key("titles")
                                             .all(titles)
                                             .addMessageOnSuccess("A custom Success message!")
                                             .validate();

  • When a validation fails, an Error Validation Message is generated by default. If you want the failure of a validation to generate a Warning Validation Message instead, use the "treatErrorAsWarning()" method. But remember that a Validation Set containing Warning Validation Message is still considered as being valid, only Error Validation Messages make it invalid! For example :

    ValidationSet myValidatorSet = validationFactory.createValidationSet();
    
    String username = "";
    
    ValidationSet lastResult = myValidatorSet.validationNotBlank()
                                             .key("username")
                                             .element(username)
                                             .treatErrorAsWarning()
                                             .validate();
                                             
    // There is now one *Warning* Validation Message
    // in our Validation Set
    myValidatorSet.getMessages().size(); // == 1
    
    // The level of the Validation Set is "Warning"
    myValidatorSet.isWarning(); // true
    myValidatorSet.isError(); // false
    myValidatorSet.isSuccess(); // false
    
    // The Validation Set is still valid!
    myValidatorSet.isValid(); // true

  • You can specify the text to use for a Validation Message when a validation fails, using the "failMessageText(...)" method. The specified text will then be used for the Error Validation Message if one is generated, but also for a Warning Validation Message if "treatErrorAsWarning()" is used.
  • You can specify that a validation must not be run if some Validation Messages at a specific level already exist in the Validation Set.

    Let's say for example that you want to validate that a username contains at least 3 characters but you only want to perform this validation if the username is not blank in the first place! You could do this programmatically, but you can also pass a special parameter to the validate(...) method :

    ValidationSet myValidatorSet = validationFactory.createValidationSet();
    
    String username = "";
    
    ValidationSet lastResult = myValidatorSet.validationNotBlank()
                                             .key("username")
                                             .element(username)
                                             .validate();
    
    // This validation is only going to be performed if
    // the Validation Set doesn't contain any errors yet                            
    lastResult = myValidatorSet.validationMinLength(3)
                               .key("username")
                               .element(username)
                               .validate(ValidationLevel.ERROR);
    

    In this example, the resulting Validation Set will only contain one Validation Message : the one for the "Not Blank" validation. The "Minimum Length" validation is not performed since the Validation Set already contains a Validation Message at the Error level!

    Also note that "validate(true)" is a shortcut for "validate(ValidationLevel.ERROR)". It means "Only run this validation is the Validation Set is still valid!".

If you are validating a JsonArray, by using the "all(...)" method, then a couple of extra methods are also available :

  • arrayItselfAddFailMessage() : if you call this method, a Validation Message will be added for the array itself if the validation for at least one of its elements fails. The validation key for this message will be the specified validation key (remember that the keys for the elements of the array are going to be the specified validation key + the position of the elements in the array).

    You can also specify the text to use for that message (instead of the default one) : arrayItselfAddFailMessage("Some elements are invalid!")

  • arrayItselfAddSuccessMessage() : if you call this method, a Validation Message will be added for the array itself if the validation is successful for all its elements. The validation key for this message will be the specified validation key (remember that the keys for the elements of the array are the specified validation key + the position of the elements in the array).

    You can also specify the text to use for the message (instead of the default one) : arrayItselfAddSuccessMessage("All good!")

JsonObject validation

Validating a JsonObject is very easy : you simply use its "validationSet()" method to get a Validation Set specifically made to validate it. For example :

JsonObject user = getUser();
JsonObjectValidationSet userValidationSet = user.validationSet();
ValidationSet lastResult = userValidationSet.validationEmail()
                                            .jsonPath("email")
                                            .validate();

Explanation :

  • 2 : When you get a Validation Set from a JsonObject, the type of this set is JsonObjectValidationSet. This type extends the ValidationSet base type and adds some extra functionalities.
  • 4 : Because this Validation Set is bound to a specific JsonObject, a new "jsonPath(...)" method is available! It allows you to specify the JsonPath to the element to validate on the current object, and will also be used as the validation key.

By calling the "validationSet()" method on a JsonObject, you get a JsonObjectValidationSet instance. This type extends the ValidationSet base type and adds some extra functionalities.

With a JsonObjectValidationSet, you don't need to specify a "key(...)" to use or an "element(...)" to validate! You simply specify the jsonPath(...) to the element to validate. The resulting Validation Messages, if any, will have that JsonPath as their validation keys. For example :

JsonObject user = getJsonManager().create();
user.set("email", "nope");

JsonObjectValidationSet userValidationSet = user.validationSet();

// The Validation Set is bound to the "user" object here,
// so we use a JsonPath starting from this object as the root
// to target the "email" element to validate 
ValidationSet lastResult = userValidationSet.validationEmail()
                                            .jsonPath("email")
                                            .validate();

// The generated validation key is the JsonPath of
// the validated element!
List<:ValidationMessage> messages = userValidationSet.getMessages("email");
System.out.println(messages.size()); // prints "1"

ValidationMessage message = messages.get(0);
System.out.println(message.getValidationLevel()); // prints "ERROR"
System.out.println(message.getCode()); // prints "VALIDATION_TYPE_EMAIL"
System.out.println(message.getText()); // prints "Invalid email address"

Finally, note that there is also a "jsonPathAll(...)" method to validate all the elements of a JsonArray.

Sharing / Merging Validation Sets

You can merge a Validation Set into another one using the mergeValidationSet() method. This allows you to make some elements being validated by an external validator and then merge the resulting Validation Messages in your local Validation Set.

Let's say you have a CompanyValidator object that provides a validateCompany(...) method able to validate a "company" JsonObject :

// The user object we're going to validate
JsonObject user = getUser();

// The local Validation Set bound to our "user" object
JsonObjectValidationSet userValidationSet = user.validationSet();

// We validate the email of the user directly
ValidationSet lastResult = userValidationSet.validationEmail()
                                            .jsonPath("email")
                                            .validate();
                                            
// We use an *external validator* to validate the company
// of the user
ValidationSet companyValidationSet = 
        getCompanyValidator().validateCompany(user.getJsonObject("company"));

// We merge the Validation Set of the company validation
// to our local set!
userValidationSet.mergeValidationSet("company.", companyValidationSet);

Explanation :

  • 2 : Let's say this is a user object containing an email and a company field, and we want to validate it.
  • 5 : We get the Validation Set of the user : this set will contain all the Validation Messages at the end of the validation process.
  • 8-10 : We validate the email of the user using a predefined validation on our local Validation Set.
  • 14-15 : We use an external validator to validate the "company" element of the user! This validation returns a Validation Set containing the Validation Messages resulting from this company validation.
  • 19 : We merge the Validation Set for the company element into our user Validation Set, and we scope its validation keys using the "company." prefix.

    At this point, the user Validation Set contain both the Validation Messages for the email field, and the Validation Messages for the company field. If printed, the set would look something like this, given that both the email and the company were invalid :

    messages = 
    {
        email = [Invalid email address - VALIDATION_TYPE_EMAIL] java.util.ArrayList
        company.name = [Can't be empty - VALIDATION_TYPE_NOT_BLANK] java.util.ArrayList
    }

When you merge two Validation Sets, don't forget to scope the merged validation keys (if required)... Learn more about this in the Validation Keys section!

Being able to share and merge Validation Sets allows you to be creative and structure your application as you wish. You can have standalone validators that are used in multiple places and situations.

Converting to a JsonObject

When your validation is done, you will want to use the Validation Messages saved in the Validation Set. In some cases, you need to serialize them to Json, for example to send them as a response to an Ajax call.

To do that, you simply call the convertToJsonObject() method on the Validation Set. This will convert the set to a JsonObject which, in turn, can easily be serialized to a plain Json string.

When you convert a Validation Set to a JsonObject, an extra root element is added (in addition to the Validation Messages) : "_". This special "_" element summarizes the validation performed using the Validation Set. It contains those fields :

  • isValid : Is the whole set valid? It is if it contains no Error Validation Messages.
  • hasErrors : Does the set contain Error Validation Messages?
  • hasWarnings : Does the set contain Warning Validation Messages?
  • hasSuccesses : Does the set contain Success Validation Messages?

Have a look at the Displaying validation messages section to see the use of this "_" element in action!





© 2015 - 2024 Weber Informatics LLC | Privacy Policy