Configuration de validation DTO dynamique extraite de la base de données

Configuration de validation DTO dynamique extraite de la base de données

1. Vue d'ensemble

Dans ce didacticiel, nous allons voir comment nous pouvonscreate a custom validation annotation that uses a regular expression retrieved from a database to match against the field value.

Nous utiliserons Hibernate Validator comme implémentation de base.

2. Dépendances Maven

Pour le développement, nous aurons besoin des dépendances suivantes:


    org.springframework.boot
    spring-boot-starter-thymeleaf
    2.0.1.RELEASE


    org.springframework.boot
    spring-boot-starter-data-jpa
    2.0.1.RELEASE

Les dernières versions despring-boot-starter-thymeleaf,spring-boot-starter-data-jpa peuvent être téléchargées depuis Maven Central.

3. Annotation de validation personnalisée

Pour notre exemple, nous allons créer une annotation personnalisée appelée@ContactInfo qui validera une valeur par rapport à une expression régulière extraite d'une base de données. Nous appliquerons ensuite cette validation sur le champcontactInfo d'une classe POJO appeléeCustomer.

Pour récupérer des expressions régulières d'une base de données, nous les modéliserons comme une classe d'entitéContactInfoExpression.

3.1. Modèles de données et référentiel

Créons la classeCustomer avec les champsid etcontactInfo:

@Entity
public class Customer {

    @Id
    @GeneratedValue(strategy = GenerationType.IDENTITY)
    private long id;

    private String contactInfo;

    // standard constructor, getters, setters
}

Ensuite, jetons un œil à la classeContactInfoExpression - qui contiendra les valeurs des expressions régulières dans une propriété appeléepattern:

@Entity
public class ContactInfoExpression {

    @Id
    @Column(name="expression_type")
    private String type;

    private String pattern;

    //standard constructor, getters, setters
}

Ensuite, ajoutons une interface de référentiel basée sur Spring Data pour manipuler les entitésContactInfoExpression:

public interface ContactInfoExpressionRepository
  extends Repository {

    Optional findById(String id);
}

3.2. Configuration de la base de données

Pour stocker les expressions régulières, nous utiliserons une base de données en mémoireH2 avec la configuration de persistance suivante:

@EnableJpaRepositories("com.example.dynamicvalidation.dao")
@EntityScan("com.example.dynamicvalidation.model")
@Configuration
public class PersistenceConfig {

    @Bean
    public DataSource dataSource() {
        EmbeddedDatabaseBuilder builder = new EmbeddedDatabaseBuilder();
        EmbeddedDatabase db = builder.setType(EmbeddedDatabaseType.H2)
          .addScript("schema-expressions.sql")
          .addScript("data-expressions.sql")
          .build();
        return db;
    }
}

Les deux scripts mentionnés sont utilisés pour créer le schéma et insérer les données dans la tablecontact_info_expression:

CREATE TABLE contact_info_expression(
  expression_type varchar(50) not null,
  pattern varchar(500) not null,
  PRIMARY KEY ( expression_type )
);

Le scriptdata-expressions.sql ajoutera trois enregistrements pour représenter les typesemail,phone, etwebsite. Celles-ci représentent des expressions régulières permettant de valider que cette valeur est une adresse électronique valide, un numéro de téléphone américain valide ou une URL valide:

insert into contact_info_expression values ('email',
  '[a-z0-9!#$%&*+/=?^_`{|}~-]+(?:\.[a-z0-9!#$%&*+/=?^_`{|}~-]+)*@(?:[a-z0-9](?:[a-z0-9-]*[a-z0-9])?\.)+[a-z0-9](?:[a-z0-9-]*[a-z0-9])?')
insert into contact_info_expression values ('phone',
  '^([0-9]( |-)?)?(\(?[0-9]{3}\)?|[0-9]{3})( |-)?([0-9]{3}( |-)?[0-9]{4}|[a-zA-Z0-9]{7})$')
insert into contact_info_expression values ('website',
  '^(http:\/\/www\.|https:\/\/www\.|http:\/\/|https:\/\/)?[a-z0-9]+([\-\.]{1}[a-z0-9]+)*\.[a-z]{2,5}(:[0-9]{1,5})?(\/.*)?$')

3.3. Création du validateur personnalisé

Créons la classeContactInfoValidator qui contient la logique de validation réelle. Conformément aux directives de spécification de validation Java, la classeimplements the ConstraintValidator interface et remplace la méthodeisValid().

Cette classe obtiendra la valeur du type d'informations de contact actuellement utilisé -email,phone, ouwebsite - qui est défini dans une propriété appeléecontactInfoType, puis l'utilise pour récupérez la valeur de l'expression régulière de la base de données:

public class ContactInfoValidator implements ConstraintValidator {

    private static final Logger LOG = Logger.getLogger(ContactInfoValidator.class);

    @Value("${contactInfoType}")
    private String expressionType;

    private String pattern;

    @Autowired
    private ContactInfoExpressionRepository expressionRepository;

    @Override
    public void initialize(ContactInfo contactInfo) {
        if (StringUtils.isEmptyOrWhitespace(expressionType)) {
            LOG.error("Contact info type missing!");
        } else {
            pattern = expressionRepository.findById(expressionType)
              .map(ContactInfoExpression::getPattern).get();
        }
    }

    @Override
    public boolean isValid(String value, ConstraintValidatorContext context) {
        if (!StringUtils.isEmptyOrWhitespace(pattern)) {
            return Pattern.matches(pattern, value);
        }
        LOG.error("Contact info pattern missing!");
        return false;
    }
}

La propriétécontactInfoType peut être définie dans le fichierapplication.properties sur l'une des valeursemail,phone ouwebsite:

contactInfoType=email

3.4. Création de l'annotation de contrainte personnalisée

Et maintenant, créons l'interface d'annotation pour notre contrainte personnalisée:

@Constraint(validatedBy = { ContactInfoValidator.class })
@Target({ METHOD, FIELD, ANNOTATION_TYPE, CONSTRUCTOR, PARAMETER })
@Retention(RetentionPolicy.RUNTIME)
public @interface ContactInfo {
    String message() default "Invalid value";

    Class[] groups() default {};

    Class[] payload() default {};
}

3.5. Application de la contrainte personnalisée

Enfin, ajoutons des annotations de validation au champcontactInfo de notre classeCustomer:

public class Customer {

    // ...
    @ContactInfo
    @NotNull
    private String contactInfo;

    // ...
}

4. Contrôleur Spring et formulaire HTML

Pour tester notre annotation de validation, nous allons créer un mappage de requête Spring MVC qui utilise l'annotation@Valid pour déclencher la validation d'un objetCustomer:

@PostMapping("/customer")
public String validateCustomer(@Valid Customer customer, BindingResult result, Model model) {
    if (result.hasErrors()) {
        model.addAttribute("message", "The information is invalid!");
    } else {
        model.addAttribute("message", "The information is valid!");
    }
    return "customer";
}

L'objetCustomer est envoyé au contrôleur à partir d'un formulaire HTML:

Contact Info:

Pour conclure, nous pouvons exécuter notre application en tant qu'application Spring Boot:

@SpringBootApplication
public class DynamicValidationApp {
    public static void main(String[] args) {
        SpringApplication.run(DynamicValidationApp.class, args);
    }
}

5. Conclusion

Dans cet exemple, nous avons montré comment créer une annotation de validation personnalisée qui extrait dynamiquement une expression régulière d'une base de données et l'utilise pour valider le champ annoté.

Le code source complet de l'exemple peut être trouvéover on GitHub.