Configuração de validação dinâmica de DTO recuperada do banco de dados

Configuração de validação dinâmica de DTO recuperada do banco de dados

1. Visão geral

Neste tutorial, vamos dar uma olhada em como podemoscreate a custom validation annotation that uses a regular expression retrieved from a database to match against the field value.

Usaremos o Hibernate Validator como uma implementação básica.

2. Dependências do Maven

Para o desenvolvimento, precisaremos das seguintes dependências:


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


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

As versões mais recentes despring-boot-starter-thymeleaf,spring-boot-starter-data-jpa podem ser baixadas do Maven Central.

3. Anotação de validação personalizada

Para nosso exemplo, criaremos uma anotação personalizada chamada@ContactInfo que validará um valor em relação a uma expressão regular recuperada de um banco de dados. Em seguida, aplicaremos essa validação no campocontactInfo de uma classe POJO chamadaCustomer.

Para recuperar expressões regulares de um banco de dados, vamos modelá-las como uma classe de entidadeContactInfoExpression.

3.1. Modelos de dados e repositório

Vamos criar a classeCustomer com os camposidecontactInfo:

@Entity
public class Customer {

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

    private String contactInfo;

    // standard constructor, getters, setters
}

A seguir, vamos dar uma olhada na classeContactInfoExpression - que conterá os valores da expressão regular em uma propriedade chamadapattern:

@Entity
public class ContactInfoExpression {

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

    private String pattern;

    //standard constructor, getters, setters
}

A seguir, vamos adicionar uma interface de repositório baseada em Spring Data para manipular as entidadesContactInfoExpression:

public interface ContactInfoExpressionRepository
  extends Repository {

    Optional findById(String id);
}

3.2. Configuração do banco de dados

Para armazenar expressões regulares, usaremos um banco de dados na memóriaH2 com a seguinte configuração de persistência:

@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;
    }
}

Os dois scripts mencionados são usados ​​para criar o esquema e inserir os dados na tabelacontact_info_expression:

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

O scriptdata-expressions.sql adicionará três registros para representar os tiposemail,phone,ewebsite. Eles representam expressões regulares para validar esse valor: um endereço de email válido, um número de telefone americano válido ou um URL válido:

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. Criação do validador personalizado

Vamos criar a classeContactInfoValidator que contém a lógica de validação real. Seguindo as diretrizes de especificação de validação Java, a classeimplements the ConstraintValidator interfacee substitui o métodoisValid().

Esta classe obterá o valor do tipo de informação de contato usado atualmente -email,phone, ouwebsite - que é definido em uma propriedade chamadacontactInfoType, então use-o para recupere o valor da expressão regular do banco de dados:

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;
    }
}

A propriedadecontactInfoType pode ser definida no arquivoapplication.properties para um dos valoresemail,phone ouwebsite:

contactInfoType=email

3.4. Criando a anotação de restrição personalizada

E agora, vamos criar a interface de anotação para nossa restrição personalizada:

@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. Aplicando a restrição personalizada

Finalmente, vamos adicionar anotações de validação ao campocontactInfo de nossa classeCustomer:

public class Customer {

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

    // ...
}

4. Spring Controller e formulário HTML

Para testar nossa anotação de validação, criaremos um mapeamento de solicitação Spring MVC que usa a anotação@Valid para acionar a validação de um objetoCustomer:

@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";
}

O objetoCustomer é enviado ao controlador a partir de um formulário HTML:

Contact Info:

Para finalizar, podemos executar nosso aplicativo como um aplicativo Spring Boot:

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

5. Conclusão

Neste exemplo, mostramos como podemos criar uma anotação de validação personalizada que recupera dinamicamente uma expressão regular de um banco de dados e a utiliza para validar o campo anotado.

O código-fonte completo do exemplo pode ser encontradoover on GitHub.