Pular para conteúdo

d5-framework-infraestrutura

Possui classes para implementar, com Spring Data JPA, o repositório e o repositório de aplicação de um agregado:

Entidade JPA

IAgregadoJpa

Marca uma classe como a entidade JPA correspondente a um agregado A. Não acrescenta métodos: apenas documenta, pelo parâmetro de tipo, qual agregado a entidade representa.

IAgregadoJpa.java
package dev.sauloaraujo.d5.framework.infraestrutura;

import dev.sauloaraujo.d5.framework.dominio.IAgregado;

public interface IAgregadoJpa<A extends IAgregado<?>> {
}

No tutorial, UnidadeFederativaJpa implementa IAgregadoJpa<UnidadeFederativa>.

Repositório

AgregadoRepositorioAbstrato

Implementação base de IAgregadoRepositorio sobre um JpaRepository do Spring Data. É parametrizada pelo identificador I1 do agregado, pelo agregado A1, pelo identificador I2 e pela entidade A2 — uma IAgregadoJpa<A1> — usados pelo Spring Data. Recebe no construtor o JpaRepository<A2, I2> e o modelo da mensagem de erro para identificador inexistente — no formato de String.format, com um %s substituído pelo identificador, usado por buscarPorIdentificador ao lançar AgregadoInexistenteExcecao — e implementa incluir, buscarPorIdentificador, alterar, excluir e existePorIdentificador convertendo entre agregado e entidade através dos métodos abstratos paraJpa, paraAgregado e paraIdentificadorJpa, que a subclasse implementa.

AgregadoRepositorioAbstrato.java
package dev.sauloaraujo.d5.framework.infraestrutura;

import java.util.Objects;

import org.springframework.data.jpa.repository.JpaRepository;

import dev.sauloaraujo.d5.framework.dominio.AgregadoInexistenteExcecao;
import dev.sauloaraujo.d5.framework.dominio.IAgregado;
import dev.sauloaraujo.d5.framework.dominio.IIdentificador;
import dev.sauloaraujo.d5.framework.dominio.spi.IAgregadoRepositorio;

public abstract class AgregadoRepositorioAbstrato<I1 extends IIdentificador, A1 extends IAgregado<I1>, I2, A2 extends IAgregadoJpa<A1>>
        implements IAgregadoRepositorio<I1, A1> {
    private final JpaRepository<A2, I2> repositorioJpa;
    private final String inexistenteModelo;

    protected AgregadoRepositorioAbstrato(JpaRepository<A2, I2> repositorioJpa, String inexistenteModelo) {
        this.repositorioJpa = Objects.requireNonNull(repositorioJpa);
        this.inexistenteModelo = Objects.requireNonNull(inexistenteModelo);
    }

    @Override
    public A1 incluir(A1 agregado) {
        Objects.requireNonNull(agregado);
        var entidadeJpa = repositorioJpa.save(paraJpa(agregado));
        return paraAgregado(entidadeJpa);
    }

    protected abstract A2 paraJpa(A1 agregado);

    protected abstract A1 paraAgregado(A2 entidadeJpa);

    @Override
    public A1 buscarPorIdentificador(I1 identificador) {
        Objects.requireNonNull(identificador);
        var identificadorJpa = paraIdentificadorJpa(identificador);
        var entidadeJpa = repositorioJpa.findById(identificadorJpa).orElseThrow(() -> {
            var mensagem = inexistenteModelo.formatted(identificador);
            return new AgregadoInexistenteExcecao(mensagem);
        });
        return paraAgregado(entidadeJpa);
    }

    protected abstract I2 paraIdentificadorJpa(I1 identificador);

    @Override
    public void alterar(A1 agregado) {
        Objects.requireNonNull(agregado);
        repositorioJpa.save(paraJpa(agregado));
    }

    @Override
    public void excluir(I1 identificador) {
        Objects.requireNonNull(identificador);
        repositorioJpa.deleteById(paraIdentificadorJpa(identificador));
    }

    @Override
    public boolean existePorIdentificador(I1 identificador) {
        Objects.requireNonNull(identificador);
        return repositorioJpa.existsById(paraIdentificadorJpa(identificador));
    }
}

No tutorial, UnidadeFederativaRepositorio estende AgregadoRepositorioAbstrato e implementa paraJpa, paraAgregado e paraIdentificadorJpa.

Repositório de aplicação

AgregadoAplicacaoRepositorioAbstrato

Implementação base de IAgregadoAplicacaoRepositorio sobre Spring Data JPA. É parametrizada pela projeção P, pela ordenação O e pela consulta C. Implementa pesquisar(C) convertendo a consulta em um Pageable — cuja ordenação é resolvida pelo método abstrato paraPropriedadeOrdenacao, que a subclasse implementa — e delegando ao método abstrato pesquisar(C, Pageable), que a subclasse também implementa consultando o Spring Data. Na ausência de ordenação na consulta, a página não é ordenada.

Também expõe paraLike, que converte um filtro textual opcional em um padrão para cláusulas like que encontra o filtro em qualquer posição: converte o filtro para minúsculas, escapa %, _ e \ com o caractere de escape \ e o envolve com %; na ausência de filtro, devolve null. Para que o padrão funcione, a consulta JPQL que o recebe deve, portanto, comparar com a coluna em minúsculas, declarar o caractere de escape e ignorar o filtro quando ele for null — por exemplo, :nome is null or lower(u.nome) like :nome escape '\'.

AgregadoAplicacaoRepositorioAbstrato.java
package dev.sauloaraujo.d5.framework.infraestrutura;

import java.util.Objects;
import java.util.Optional;

import org.jspecify.annotations.Nullable;
import org.springframework.data.domain.Page;
import org.springframework.data.domain.PageRequest;
import org.springframework.data.domain.Pageable;
import org.springframework.data.domain.Sort;
import org.springframework.data.jpa.repository.query.EscapeCharacter;

import dev.sauloaraujo.d5.framework.aplicacao.IConsulta;
import dev.sauloaraujo.d5.framework.aplicacao.IConsultaResultado;
import dev.sauloaraujo.d5.framework.aplicacao.IOrdenacao;
import dev.sauloaraujo.d5.framework.aplicacao.IProjecao;
import dev.sauloaraujo.d5.framework.aplicacao.impl.ConsultaResultado;
import dev.sauloaraujo.d5.framework.aplicacao.spi.IAgregadoAplicacaoRepositorio;

public abstract class AgregadoAplicacaoRepositorioAbstrato<P extends IProjecao<?>, O extends Enum<O> & IOrdenacao<P>, C extends IConsulta<O>>
        implements IAgregadoAplicacaoRepositorio<P, C> {
    @Override
    public IConsultaResultado<P> pesquisar(C consulta) {
        Objects.requireNonNull(consulta);
        var pagina = pesquisar(consulta, paraPageable(consulta));
        return new ConsultaResultado<>(pagina.getContent(), pagina.getTotalElements());
    }

    protected abstract Page<P> pesquisar(C consulta, Pageable pageable);

    protected abstract String paraPropriedadeOrdenacao(O ordenacao);

    protected @Nullable String paraLike(Optional<String> trechoOpcional) {
        Objects.requireNonNull(trechoOpcional);
        return trechoOpcional.map(trecho -> "%" + EscapeCharacter.DEFAULT.escape(trecho.toLowerCase()) + "%")
                .orElse(null);
    }

    private Pageable paraPageable(C consulta) {
        var indicePagina = consulta.getIndicePagina();
        var tamanhoPagina = consulta.getTamanhoPagina();
        var ordenacao = paraOrdenacao(consulta);
        return PageRequest.of(indicePagina, tamanhoPagina, ordenacao);
    }

    private Sort paraOrdenacao(C consulta) {
        return consulta.getOrdenacao().map(ordenacao -> {
            var direcao = consulta.isDescendente() ? Sort.Direction.DESC : Sort.Direction.ASC;
            return Sort.by(direcao, paraPropriedadeOrdenacao(ordenacao));
        }).orElse(Sort.unsorted());
    }
}

No tutorial, UnidadeFederativaAplicacaoRepositorio estende AgregadoAplicacaoRepositorioAbstrato, implementa pesquisar(UnidadeFederativaConsulta, Pageable) e paraPropriedadeOrdenacao, e usa paraLike para filtrar pela sigla e pelo nome. Já MunicipioAplicacaoRepositorio mapeia, em paraPropriedadeOrdenacao, a ordenação pelo nome da unidade federativa para uma propriedade qualificada pelo alias de uma junção da consulta.

Validação

ValidacaoConfiguracao

Fornece ao Hibernate, através de um HibernatePropertiesCustomizer, a ValidatorFactory que ele usa para validar as entidades JPA antes de gravá-las, configurada com a localidade pt-BR, para que as mensagens das violações fiquem em português independentemente da localidade padrão da JVM. Fecha a ValidatorFactory quando o contexto do Spring é encerrado. É registrada como autoconfiguração do módulo. Veja Validação das entidades JPA.

ValidacaoConfiguracao.java
package dev.sauloaraujo.d5.framework.infraestrutura;

import java.util.Locale;

import org.hibernate.cfg.ValidationSettings;
import org.hibernate.validator.HibernateValidator;
import org.springframework.beans.factory.DisposableBean;
import org.springframework.boot.hibernate.autoconfigure.HibernatePropertiesCustomizer;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;

import jakarta.validation.Validation;
import jakarta.validation.ValidatorFactory;

@Configuration
public class ValidacaoConfiguracao implements DisposableBean {
    private final ValidatorFactory fabricaValidador = Validation.byProvider(HibernateValidator.class).configure()
            .defaultLocale(Locale.of("pt", "BR")).buildValidatorFactory();

    @Bean
    HibernatePropertiesCustomizer validacaoEntidadesJpa() {
        return propriedades -> propriedades.put(ValidationSettings.JAKARTA_VALIDATION_FACTORY, fabricaValidador);
    }

    @Override
    public void destroy() {
        fabricaValidador.close();
    }
}

Segurança

SegurancaServico

Implementação de ISegurancaServico sobre o Spring Security: lê o Authentication do SecurityContextHolder, lançando NaoAutenticadoExcecao quando não há usuário autenticado — inclusive quando a autenticação é anônima.

SegurancaServico.java
package dev.sauloaraujo.d5.framework.infraestrutura;

import java.util.List;

import org.springframework.security.authentication.AnonymousAuthenticationToken;
import org.springframework.security.core.Authentication;
import org.springframework.security.core.GrantedAuthority;
import org.springframework.security.core.context.SecurityContextHolder;

import dev.sauloaraujo.d5.framework.dominio.spi.ISegurancaServico;
import dev.sauloaraujo.d5.framework.dominio.spi.NaoAutenticadoExcecao;

public class SegurancaServico implements ISegurancaServico {
    @Override
    public String getUsuario() {
        return getAuthentication().getName();
    }

    private Authentication getAuthentication() {
        var contexto = SecurityContextHolder.getContext();
        var autenticacao = contexto.getAuthentication();
        if (autenticacao == null || !autenticacao.isAuthenticated()
                || autenticacao instanceof AnonymousAuthenticationToken) {
            throw new NaoAutenticadoExcecao("Usuário não autenticado");
        }
        return autenticacao;
    }

    @Override
    public List<String> getAutorizacoes() {
        return getAuthentication().getAuthorities().stream().map(GrantedAuthority::getAuthority).toList();
    }
}

Diferente das implementações de agregado, que o registro de beans descobre por herança de interface, SegurancaServico é registrado diretamente como autoconfiguração do módulo — é a única implementação de ISegurancaServico esperada em uma aplicação D5.

Registro de beans

AgregadoRegistrador

Registra como beans, sem que precisem de anotações do Spring, as implementações de IAgregadoRepositorio, IAgregadoAplicacaoRepositorio, IAgregadoServico e IAgregadoAplicacaoServico. É registrado como autoconfiguração do módulo. Veja Registro de beans para os detalhes e o código.