Skip to content

Instantly share code, notes, and snippets.

@josergdev
Last active August 24, 2024 10:51
Show Gist options
  • Save josergdev/84da286f32c4d93a113b1e33bfe65da6 to your computer and use it in GitHub Desktop.
Save josergdev/84da286f32c4d93a113b1e33bfe65da6 to your computer and use it in GitHub Desktop.

Revisions

  1. josergdev renamed this gist Aug 24, 2024. 1 changed file with 0 additions and 0 deletions.
  2. josergdev revised this gist Aug 24, 2024. No changes.
  3. josergdev created this gist Aug 24, 2024.
    92 changes: 92 additions & 0 deletions SimpleHttpCriteria using Specification JPA
    Original file line number Diff line number Diff line change
    @@ -0,0 +1,92 @@
    package dev.joserg.jpa;

    import java.util.HashMap;
    import java.util.List;
    import java.util.Map;
    import java.util.Map.Entry;

    import jakarta.persistence.criteria.CriteriaBuilder;
    import jakarta.persistence.criteria.CriteriaQuery;
    import jakarta.persistence.criteria.JoinType;
    import jakarta.persistence.criteria.Path;
    import jakarta.persistence.criteria.Predicate;
    import jakarta.persistence.criteria.Root;
    import jakarta.persistence.metamodel.ListAttribute;
    import jakarta.persistence.metamodel.SingularAttribute;
    import org.springframework.data.jpa.domain.Specification;

    public record SimpleHttpCriteria<E>(
    Map<PathMaker<E, ?>, List<?>> filters,
    Operator internalOperator,
    Operator externalOperator) implements Specification<E> {

    public SimpleHttpCriteria() {
    this(new HashMap<>(), Operator.OR, Operator.AND);
    }

    @Override
    public Predicate toPredicate(Root<E> root, CriteriaQuery<?> query, CriteriaBuilder criteriaBuilder) {
    return this.specification().toPredicate(root, query, criteriaBuilder);
    }

    public <J> SimpleHttpCriteria<E> filter(PathMaker<E, J> path, List<J> values) {
    if (!values.isEmpty()) {
    this.filters.put(path, values);
    }
    return this;
    }

    public SimpleHttpCriteria<E> internalOperator(Operator operator) {
    return new SimpleHttpCriteria<>(this.filters, operator, this.externalOperator);
    }

    public SimpleHttpCriteria<E> externalOperator(Operator operator) {
    return new SimpleHttpCriteria<>(this.filters, this.internalOperator, operator);
    }

    private Specification<E> specification() {
    final var filtersSpecs = this.filters.entrySet().stream().map(this::byFilter).toList();
    return this.externalOperator.reduce(filtersSpecs);
    }

    private Specification<E> byFilter(Entry<PathMaker<E, ?>, List<?>> filter) {
    final var filterSpecs = filter.getValue().stream().map(value -> this.byObject(filter.getKey(), value)).toList();
    return this.internalOperator.reduce(filterSpecs);
    }

    private Specification<E> byObject(PathMaker<E, ?> path, Object value) {
    return (root, query, criteriaBuilder) -> criteriaBuilder.equal(path.toPath(root), value);

    }

    @FunctionalInterface
    public interface PathMaker<R, P> {
    static <R, P> PathMaker<R, P> attr(final SingularAttribute<? super R, P> attribute) {
    return root -> root.get(attribute);
    }

    static <R, J, P> PathMaker<R, P> join(final ListAttribute<? super R, J> list,
    final SingularAttribute<? super J, P> attribute,
    JoinType joinType) {
    return root -> root.join(list, joinType).get(attribute);
    }

    static <R, J, P> PathMaker<R, P> joinLeft(final ListAttribute<? super R, J> list,
    final SingularAttribute<? super J, P> attribute) {
    return join(list, attribute, JoinType.LEFT);
    }

    Path<P> toPath(Root<R> root);
    }

    public enum Operator {
    AND, OR;

    public <E> Specification<E> reduce(Iterable<Specification<E>> specifications) {
    return switch (this) {
    case AND -> Specification.allOf(specifications);
    case OR -> Specification.anyOf(specifications);
    };
    }
    }
    }