package br.gov.servicos.busca; import br.gov.servicos.cms.PaginaEstatica; import lombok.experimental.FieldDefaults; import lombok.extern.slf4j.Slf4j; import org.elasticsearch.index.query.QueryBuilder; import org.elasticsearch.search.SearchHitField; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.cache.annotation.Cacheable; import org.springframework.data.domain.Page; import org.springframework.data.domain.PageRequest; import org.springframework.data.elasticsearch.core.ElasticsearchTemplate; import org.springframework.data.elasticsearch.core.FacetedPageImpl; import org.springframework.data.elasticsearch.core.query.NativeSearchQueryBuilder; import org.springframework.stereotype.Component; import java.util.List; import java.util.Objects; import java.util.Optional; import java.util.function.Function; import java.util.stream.Stream; import static br.gov.servicos.cms.TipoPagina.*; import static br.gov.servicos.config.PortalDeServicosIndex.PORTAL_DE_SERVICOS_INDEX; import static java.lang.Integer.MAX_VALUE; import static java.util.Collections.emptyList; import static java.util.stream.Collectors.toList; import static lombok.AccessLevel.PRIVATE; import static org.elasticsearch.common.unit.Fuzziness.ONE; import static org.elasticsearch.index.query.QueryBuilders.*; @Component @Slf4j @FieldDefaults(level = PRIVATE, makeFinal = true) @Cacheable("buscas") public class BuscadorConteudo { private static final FacetedPageImpl<PaginaEstatica> SEM_RESULTADOS = new FacetedPageImpl<>(emptyList()); private static final int PAGE_SIZE = 20; ElasticsearchTemplate et; @Autowired BuscadorConteudo(ElasticsearchTemplate et) { this.et = et; } public Page<PaginaEstatica> busca(Optional<String> termoBuscado, Integer paginaAtual) { log.debug("Executando busca simples por '{}'", termoBuscado.orElse("")); return executaQuery(termoBuscado, paginaAtual, q -> boolQuery() .must(multiMatchQuery(q, "nome", "nomesPopulares", "conteudo", "descricao", "palavrasChave") .fuzziness(ONE) .prefixLength(0)) .should(matchQuery("nome", q) .boost(10)) .should(matchQuery("nomesPopulares", q) .boost(9))); } public List<PaginaEstatica> buscaSemelhante(Optional<String> termoBuscado) { return executaQuery(termoBuscado, termo -> fuzzyLikeThisQuery("nome", "conteudo", "descricao").likeText(termo)); } private List<PaginaEstatica> executaQuery(Optional<String> termoBuscado, Function<String, QueryBuilder> criaQuery) { return executaQuery(termoBuscado, 0, MAX_VALUE, criaQuery).getContent(); } private Page<PaginaEstatica> executaQuery(Optional<String> termoBuscado, Integer paginaAtual, Function<String, QueryBuilder> criaQuery) { return executaQuery(termoBuscado, paginaAtual, PAGE_SIZE, criaQuery); } private Page<PaginaEstatica> executaQuery(Optional<String> termoBuscado, Integer paginaAtual, Integer quantidadeDeResultados, Function<String, QueryBuilder> criaQuery) { Optional<String> termo = termoBuscado.filter(t -> !t.isEmpty()); PageRequest pageable = new PageRequest(paginaAtual, quantidadeDeResultados); return termo.map(criaQuery) .map(q -> et.query( new NativeSearchQueryBuilder() .withIndices(PORTAL_DE_SERVICOS_INDEX) .withTypes(ORGAO.getNome(), PAGINA_TEMATICA.getNome(), SERVICO.getNome()) .withFields("id", "tipoConteudo", "nome", "conteudo", "descricao") .withQuery(q) .withPageable(pageable) .build(), r -> new FacetedPageImpl<>(Stream.of(r.getHits().getHits()) .map(h -> new PaginaEstatica() .withId(h.field("id").value()) .withTipoConteudo((String) Optional.ofNullable(h.field("tipoConteudo")) .filter(Objects::nonNull) .map(SearchHitField::value) .orElse("servico")) .withNome(h.field("nome").value()) .withConteudo(Optional.ofNullable(h.field("descricao")) .orElse(h.field("conteudo")) .value())) .collect(toList()), pageable, r.getHits().totalHits()))) .orElse(SEM_RESULTADOS); } }