All Downloads are FREE. Search and download functionalities are using the official Maven repository.

org.springframework.data.jpa.datatables.SpecificationBuilder Maven / Gradle / Ivy

package org.springframework.data.jpa.datatables;

import jakarta.persistence.metamodel.Bindable.BindableType;
import org.springframework.data.jpa.datatables.mapping.DataTablesInput;
import org.springframework.data.jpa.domain.Specification;
import org.springframework.lang.NonNull;

import jakarta.persistence.criteria.*;
import java.util.ArrayList;
import java.util.List;

public class SpecificationBuilder extends AbstractPredicateBuilder> {
    public SpecificationBuilder(DataTablesInput input) {
        super(input);
    }

    @Override
    public Specification build() {
        return new DataTablesSpecification<>();
    }

    private class DataTablesSpecification implements Specification {
        @Override
        public Predicate toPredicate(@NonNull Root root, @NonNull CriteriaQuery query, @NonNull CriteriaBuilder criteriaBuilder) {
            Predicates predicates = new Predicates();
            initPredicatesRecursively(predicates, query, tree, root, root, criteriaBuilder);

            if (input.getSearchPanes() != null) {
                input.getSearchPanes().forEach((attribute, values) -> {
                    if (!values.isEmpty()) {
                        predicates.columns.add(root.get(attribute).in(values));
                    }
                });
            }

            return predicates.toPredicate(criteriaBuilder);
        }

        private boolean isCountQuery(CriteriaQuery query) {
            return query.getResultType() == Long.class;
        }

        protected void initPredicatesRecursively(Predicates predicates, CriteriaQuery query, Node node, From from, FetchParent fetch, CriteriaBuilder criteriaBuilder) {
            if (node.isLeaf()) {
                boolean hasColumnFilter = node.getData() != null;
                if (hasColumnFilter) {
                    Filter columnFilter = node.getData();
                    predicates.columns.add(columnFilter.createPredicate(from, criteriaBuilder, node.getName()));
                } else if (hasGlobalFilter) {
                    Filter globalFilter = tree.getData();
                    predicates.global.add(globalFilter.createPredicate(from, criteriaBuilder, node.getName()));
                }
            }
            for (Node child : node.getChildren()) {
                Path path = from.get(child.getName());
                if (path.getModel().getBindableType() == BindableType.PLURAL_ATTRIBUTE) {
                    // ignore OneToMany and ManyToMany relationships
                    continue;
                }
                if (child.isLeaf()) {
                    initPredicatesRecursively(predicates, query, child, from, fetch, criteriaBuilder);
                } else {
                    Join join = from.join(child.getName(), JoinType.LEFT);

                    if (isCountQuery(query)) {
                        initPredicatesRecursively(predicates, query, child, join, join, criteriaBuilder);
                    } else {
                        Fetch childFetch = fetch.fetch(child.getName(), JoinType.LEFT);
                        initPredicatesRecursively(predicates, query, child, join, childFetch, criteriaBuilder);
                    }
                }
            }
        }
    }

    private class DataTablesSearchPaneSpecification extends DataTablesSpecification {

        @Override
        protected void initPredicatesRecursively(Predicates predicates, CriteriaQuery query, Node node, From from,
            FetchParent fetch, CriteriaBuilder criteriaBuilder) {
            if (node.isLeaf()) {
                boolean hasColumnFilter = node.getData() != null;
                if (hasColumnFilter) {
                    Filter columnFilter = node.getData();
                    predicates.columns.add(columnFilter.createPredicate(from, criteriaBuilder, node.getName()));
                } else if (hasGlobalFilter) {
                    Filter globalFilter = tree.getData();
                    predicates.global.add(globalFilter.createPredicate(from, criteriaBuilder, node.getName()));
                }
            }
            for (Node child : node.getChildren()) {
                Path path = from.get(child.getName());
                if (path.getModel().getBindableType() == BindableType.PLURAL_ATTRIBUTE) {
                    // ignore OneToMany and ManyToMany relationships
                    continue;
                }
                if (child.isLeaf()) {
                    initPredicatesRecursively(predicates, query, child, from, fetch, criteriaBuilder);
                } else {
                    Join join = from.join(child.getName(), JoinType.LEFT);
                    initPredicatesRecursively(predicates, query, child, join, fetch, criteriaBuilder);
                }
            }
        }
    }

    public Specification buildSearchPane() {
        return new DataTablesSearchPaneSpecification<>();
    }

    private static class Predicates {
        public List columns = new ArrayList<>();
        public List global = new ArrayList<>();

        Predicate toPredicate(CriteriaBuilder criteriaBuilder) {
            if (!global.isEmpty()) {
                columns.add(criteriaBuilder.or(global.toArray(new Predicate[0])));
            }

            return columns.isEmpty() ? criteriaBuilder.conjunction() : criteriaBuilder.and(columns.toArray(new Predicate[0]));
        }
    }
}