Oval

Introduction

OVAL est un cadre de validation pragmatique et évolutif pour tous types d’objets Java (et pas seulement JavaBeans).

Les contraintes peuvent être déclarées avec des annotations (@ NotNull, @ MaxLength), POJO ou XML.

Les contraintes personnalisées peuvent être exprimées sous forme de classes Java personnalisées ou en utilisant les langages de script tels que JavaScript, Groovy, BeanShell, OGNL ou MVEL.

Pour aller plus loin, voici une trilogie d’articles expliquant comment oval est intégré dans Scub foundation et comment l’utiliser pour gérer les contrôles d’intégrités dans vos applications.

Site officiel

Vous trouverez le site officiel de Oval à l’adresse suivante : http://oval.sourceforge.net/

Tutoriel

Avant d’entamer ce tutoriel, vérifiez que vous avez bien les pré requis :

  • Le JDK doit être installé sur votre machine.
  • Eclipse doit être installé sur votre machine. Si vous ne l’avez pas installé, référez vous à ce tutoriel.

Création du projet dans Eclipse

  • Prendre le projet suivant : projet-client, avant de commencer.
  • Lancer Eclipse.
  • On crée un nouveau projet. Pour cela on ouvre le menu File → New → Other → Maven Project.
  • La fenêtre de wizard apparaît alors. Nous allons simplement cocher simple projet.
  • Puis sur la deuxième fenêtre après avoir fait Next, mettre sur Group Id : scub-foundation-tutorial-oval et dans Artifact Id : sf-tutorial-oval.
  • On peut ensuite cliquer sur Finish.

Mise en place du projet oval

Pour commencer créez un package com.scub.foundation.tutorial.oval dans src/main/java, et également un package com.scub.foundation.tutorial.oval.test dans src/test/java.

Une fois les packages créés nous allons commencer par ajouter les dépendances nécessaires à la bonne réalisation de notre projet.
Il y a deux dépendances à ajouter :

<dependencies>
    <!-- OVAL -->
    <dependency>
        <groupId>net.sf.oval</groupId>
        <artifactId>oval</artifactId>
        <version>1.81</version>
    </dependency>
    <dependency>
        <groupId>junit</groupId>
        <artifactId>junit</artifactId>
        <version>4.4</version>
        <scope>test</scope>
    </dependency>
</dependencies>

Puis il faut créer la classe où l’on va mettre des restrictions sur certains champs. Cette classe est logiquement un Dto, ici j’ai pris l’exemple d’un bateau.

Voici le code de cette classe :

package com.scub.foundation.tutorial.oval;
 
import net.sf.oval.configuration.annotation.IsInvariant;
import net.sf.oval.constraint.Length;
import net.sf.oval.constraint.NotEmpty;
import net.sf.oval.constraint.NotNegative;
import net.sf.oval.constraint.NotNull;
 
/**
 * Classe pour l'objet Bateau.
 * @author Scub-Foundation
 */
public class Bateau {
 
	/** Identifiant du navire. */
	@NotNull
	@NotEmpty
	@Length(max=32)
	private Integer id;
 
	/** Nom du navire. */
	private String nom;
 
	/** Année de contruction du navire. */
	@NotNegative (profiles = {"profile1"})
	private int anneeConstruction;
 
	/** Capitaine du navire. */
	@NotNull (profiles = {"profile1"})
	private String capitaine;
 
	/** Marin du navire. */
	@NotNull (profiles = {"profile2"})
	private String marin;
 
	/**
	 * @return the id
	 */
	public Integer getId() {
		return id;
	}
 
	/**
	 * @param id the id to set
	 */
	public void setId(Integer id) {
		this.id = id;
	}
 
	/**
	 * @return the nom
	 */
	@IsInvariant
	@NotNull 
	@Length(max = 4)
	public String getNom() {
		return nom;
	}
 
	/**
	 * @param nom the nom to set
	 */
	public void setNom(String nom) {
		this.nom = nom;
	}
 
	/**
	 * @return the anneeConstruction
	 */
	public int getAnneeConstruction() {
		return anneeConstruction;
	}
 
	/**
	 * @param anneeConstruction the anneeConstruction to set
	 */
	public void setAnneeConstruction(int anneeConstruction) {
		this.anneeConstruction = anneeConstruction;
	}
 
	/**
	 * @return the capitaine
	 */
	public String getCapitaine() {
		return capitaine;
	}
 
	/**
	 * @param capitaine the capitaine to set
	 */
	public void setCapitaine(String capitaine) {
		this.capitaine = capitaine;
	}
 
	/**
	 * @return the marin
	 */
	public String getMarin() {
		return marin;
	}
 
	/**
	 * @param marin the marin to set
	 */
	public void setMarin(String marin) {
		this.marin = marin;
	}
}

Quelques explications s’imposent.

  • Le premier champ est obligatoire, il ne peut pas être vide et ne doit pas faire plus de 32 caractères.
  • Le deuxième n’a aucune vérification.
  • Les troisième  et quatrième champs ne doivent pas être négatifs. Seulement si on est sur le profile1.
  • Le cinquième champ est obligatoire mais que pour le profile2.
  • Pour finir la méthode getNom() doit impérativement renvoyer un objet non nul et avec 4 caractères au maximum.

La dernière phase est celle des tests pour voir comment Oval va gérer les contraintes fournies à notre classe.

Dans un premier temps on va faire un test en prenant compte de tous les profils.

Validator validator = new Validator();
Bateau bateau = new Bateau(); // id is null
bateau.setNom("Titanic");
bateau.setAnneeConstruction(-1000);
 
// collect the constraint violations
List <ConstraintViolation> violations = validator.validate(bateau);
assertEquals("Il devrait y avoir une erreur", 5, violations.size());
int nbErreur = 1;
	for (ConstraintViolation constraintViolation : violations) {
		System.out.println("========= erreur n°" + nbErreur + "==========");
		System.out.println("Cause : " + constraintViolation.getCauses() + " / Check name : " + constraintViolation.getCheckName());
		System.out.println("Message : " + constraintViolation.getMessage());
		nbErreur++;
	}

Après on va faire un second test en ne gardant pas le profile1 et le profile2.  Pour cela il suffit de modifier le nombre d’erreurs attendues et d’ajouter ceci :

validator.disableProfile("profile1");
validator.disableProfile("profile2");

Les troisième et quatrième tests consistent à ne prendre qu’un seul profile. Donc il faut modifier encore une fois le nombre d’erreurs attendues et mettre : faire un seul disable d’un profil.

On peut également faire un validator.disableAllProfiles() dans ce cas là on ne prend en charge aucunes contraintes.

Voici à quoi doit ressembler la classe test :

package com.scub.foundation.tutorial.oval.test;
 
import static org.junit.Assert.assertEquals;
import java.util.List;
import net.sf.oval.ConstraintViolation;
import net.sf.oval.Validator;
import org.junit.Before;
import org.junit.Test;
import com.scub.foundation.tutorial.oval.Bateau;
 
/**
 * Classe de test.
 * @author Scub-Foundation
 */
public class TestOval {
 
	/**
	 * Méthode appelée à l'initialisation de la classe de test Unitaire.
	 */
	@Before
	public void setUp(){
		System.out.println("                                          ----------------- début du Test ----------------");
	}
 
	/**
	 * Test avec les 2 profiles.
	 * @throws Exception 
	 */
	@Test
	public void testValiderAvecProfiles() throws Exception{
		Validator validator = new Validator();
		Bateau bateau = new Bateau(); // id is null
		bateau.setNom("Titanic");
		bateau.setAnneeConstruction(-1000);
 
		// collect the constraint violations
		List <ConstraintViolation> violations = validator.validate(bateau);
		assertEquals("Il devrait y avoir une erreur", 5, violations.size());
		int nbErreur = 1;
		for (ConstraintViolation constraintViolation : violations) {
			System.out.println("========= erreur n°" + nbErreur + "==========");
			System.out.println("Cause : " + constraintViolation.getCauses() + " / Check name : " + constraintViolation.getCheckName());
			System.out.println("Message : " + constraintViolation.getMessage());
			nbErreur++;
		}
		System.out.println("\n");
		System.out.println("                                     ----------------- Fin méthode avec profiles ----------------- \n");
		System.out.println("************************************************************************************************** \n");
	}
 
	/**
	 * Test avec sans profile.
	 * @throws Exception 
	 */
	@Test
	public void testValiderSansProfile() throws Exception{
		Validator validator = new Validator();
		validator.disableProfile("profile1");
		validator.disableProfile("profile2");
		Bateau bateau = new Bateau(); // id is null
		bateau.setNom("Titanic");
		bateau.setAnneeConstruction(-1000);
 
		// collect the constraint violations
		List <ConstraintViolation> violations = validator.validate(bateau);
		assertEquals("Il devrait y avoir une erreur", 2, violations.size());
		int nbErreur = 1;
		for (ConstraintViolation constraintViolation : violations) {
			System.out.println("========= erreur n°" + nbErreur + "==========");
			System.out.println("Cause : " + constraintViolation.getCauses() + " / Check name : " + constraintViolation.getCheckName());
			System.out.println("Message : " + constraintViolation.getMessage());
			nbErreur++;
		}
		System.out.println("\n");
		System.out.println("                                     ----------------- Fin méthode sans profiles ----------------- \n");
		System.out.println("************************************************************************************************** \n");
	}
 
	/**
	 * Test avec profile 1.
	 * @throws Exception 
	 */
	@Test
	public void testValiderProfile1() throws Exception{
		Validator validator = new Validator();
		validator.disableProfile("profile2");
		Bateau bateau = new Bateau(); // id is null
		bateau.setNom("Titanic");
		bateau.setAnneeConstruction(-1000);
 
		// collect the constraint violations
		List <ConstraintViolation> violations = validator.validate(bateau);
		assertEquals("Il devrait y avoir une erreur", 4, violations.size());
		int nbErreur = 1;
		for (ConstraintViolation constraintViolation : violations) {
			System.out.println("========= erreur n°" + nbErreur + "==========");
			System.out.println("Cause : " + constraintViolation.getCauses() + " / Check name : " + constraintViolation.getCheckName());
			System.out.println("Message : " + constraintViolation.getMessage());
			nbErreur++;
		}
		System.out.println("\n");
		System.out.println("                                     ----------------- Fin méthode avec profile1 ----------------- \n");
		System.out.println("************************************************************************************************** \n");
	}
 
	/**
	 * Test avec profile 2.
	 * @throws Exception 
	 */
	@Test
	public void testValiderProfile2() throws Exception{
		Validator validator = new Validator();
		validator.disableProfile("profile1");
		Bateau bateau = new Bateau(); // id is null
		bateau.setNom("Titanic");
		bateau.setAnneeConstruction(-1000);
 
		// collect the constraint violations
		List <ConstraintViolation> violations = validator.validate(bateau);
		assertEquals("Il devrait y avoir une erreur", 3, violations.size());
		int nbErreur = 1;
		for (ConstraintViolation constraintViolation : violations) {
			System.out.println("========= erreur n°" + nbErreur + "==========");
			System.out.println("Cause : " + constraintViolation.getCauses() + " / Check name : " + constraintViolation.getCheckName());
			System.out.println("Message : " + constraintViolation.getMessage());
			nbErreur++;
		}
		System.out.println("\n");
		System.out.println("                                     ----------------- Fin méthode avec profile2 -----------------");
	}
}

Pour lancer les tests, placez vous sur le projet, clic droit, « Run As » -> « GWT JUnit Test ».
Pour finir voici le résultat attendu :

                                          ----------------- début du Test ----------------
========= erreur n°1==========
Cause : null / Check name : net.sf.oval.constraint.NotNullCheck
Message : com.scub.foundation.tutorial.oval.Bateau.id ne doit pas être null
========= erreur n°2==========
Cause : null / Check name : net.sf.oval.constraint.NotNegativeCheck
Message : com.scub.foundation.tutorial.oval.Bateau.anneeConstruction ne doit pas être négatif
========= erreur n°3==========
Cause : null / Check name : net.sf.oval.constraint.NotNullCheck
Message : com.scub.foundation.tutorial.oval.Bateau.capitaine ne doit pas être null
========= erreur n°4==========
Cause : null / Check name : net.sf.oval.constraint.NotNullCheck
Message : com.scub.foundation.tutorial.oval.Bateau.marin ne doit pas être null
========= erreur n°5==========
Cause : null / Check name : net.sf.oval.constraint.LengthCheck
Message : com.scub.foundation.tutorial.oval.Bateau.getNom() n'est pas compris entre 0 et 4 caractères de longueur
 
                                     ----------------- Fin méthode avec profiles ----------------- 
 
************************************************************************************************** 
 
                                          ----------------- début du Test ----------------
========= erreur n°1==========
Cause : null / Check name : net.sf.oval.constraint.NotNullCheck
Message : com.scub.foundation.tutorial.oval.Bateau.id ne doit pas être null
========= erreur n°2==========
Cause : null / Check name : net.sf.oval.constraint.LengthCheck
Message : com.scub.foundation.tutorial.oval.Bateau.getNom() n'est pas compris entre 0 et 4 caractères de longueur
 
                                     ----------------- Fin méthode sans profiles ----------------- 
 
************************************************************************************************** 
 
                                          ----------------- début du Test ----------------
========= erreur n°1==========
Cause : null / Check name : net.sf.oval.constraint.NotNullCheck
Message : com.scub.foundation.tutorial.oval.Bateau.id ne doit pas être null
========= erreur n°2==========
Cause : null / Check name : net.sf.oval.constraint.NotNegativeCheck
Message : com.scub.foundation.tutorial.oval.Bateau.anneeConstruction ne doit pas être négatif
========= erreur n°3==========
Cause : null / Check name : net.sf.oval.constraint.NotNullCheck
Message : com.scub.foundation.tutorial.oval.Bateau.capitaine ne doit pas être null
========= erreur n°4==========
Cause : null / Check name : net.sf.oval.constraint.LengthCheck
Message : com.scub.foundation.tutorial.oval.Bateau.getNom() n'est pas compris entre 0 et 4 caractères de longueur
 
                                     ----------------- Fin méthode avec profile1 ----------------- 
 
************************************************************************************************** 
 
                                          ----------------- début du Test ----------------
========= erreur n°1==========
Cause : null / Check name : net.sf.oval.constraint.NotNullCheck
Message : com.scub.foundation.tutorial.oval.Bateau.id ne doit pas être null
========= erreur n°2==========
Cause : null / Check name : net.sf.oval.constraint.NotNullCheck
Message : com.scub.foundation.tutorial.oval.Bateau.marin ne doit pas être null
========= erreur n°3==========
Cause : null / Check name : net.sf.oval.constraint.LengthCheck
Message : com.scub.foundation.tutorial.oval.Bateau.getNom() n'est pas compris entre 0 et 4 caractères de longueur
 
                                     ----------------- Fin méthode avec profile2 -----------------

Les profils apportent une meilleure gestion des contraintes car cela permet d’avoir une seule classe avec des profils. Suivant le profil sur lequel on est, on gère telle ou telle erreur.

Télécharger les sources

Les sources du projet Eclipse pour ce tutoriel sont téléchargeables sur Source Forge à l’adresse suivante.