tkaczmarzyk / specification-arg-resolver

An alternative API for filtering data with Spring MVC & Spring Data JPA
Apache License 2.0
658 stars 149 forks source link

Extend custom Specification #52

Closed samie820 closed 4 years ago

samie820 commented 5 years ago

Please, is it possible to add an extended Specification. For example:

@Joins({
                                                   @Join(path = "category", alias = "cat"),
                                                   @Join(path = "subCategory", alias = "subCat"),
                                                   @Join(path = "sizes", alias = "sz"),
                                                   @Join(path = "user", alias = "u")
                                           })
                                           @And({
                                                   @Spec(path = "cat.id", params = "category", spec = Equal.class),
                                                   @Spec(path = "subCat.id", params = "subCategory", spec = Equal.class),
                                                   @Spec(path = "sz.id", params = "packagingSize", spec = In.class),
                                                   @Spec(path = "title", params = "search", spec = Like.class),
                                                   @Spec(path = "status", params = "status", spec = Equal.class),
                                           }) Specification specification

        Specification<Product> productSpecification = MyProductSpec.filterByUserId(userId);
        productSpecification.and(specification);
m-cakir commented 4 years ago

Yes, it's possible. See the following example.

Order

@Entity
public class Order {
    private String status;
    private String name;
    private String surname;
    @ManyToOne
    @JoinColumn
    private User user;
}

OrderSpec

@Conjunction(
        value = {
                @Or({
                        @Spec(path = "name", params = "q", spec = LikeIgnoreCase.class),
                        @Spec(path = "surname", params = "q", spec = LikeIgnoreCase.class)
                })
        },
        and = {
                @Spec(path = "status", params = "status", spec = Equal.class)
        }
)
public interface OrderSpec extends Specification<Order> {
}

SpecBuilder

import org.springframework.data.jpa.domain.Specification;
import javax.persistence.criteria.Predicate;
import java.util.*;

public class SpecBuilder {

    public static <T> Specification<T> joinUser(final Specification<T> spec) {

        return (Specification<T>) (root, query, cb) -> {
            List<Predicate> predicateList = new ArrayList<>();

            if (spec != null) {
                predicateList.add(spec.toPredicate(root, query, cb));
            }

            predicateList.add(cb.equal(root.get("user").get("username"), SecurityContextHolder.getContext().getAuthentication().getPrincipal().toString()));

            return cb.and(predicateList.stream().filter(Objects::nonNull).toArray(Predicate[]::new));
        };
    }

}

OrderController

@RestController
@RequestMapping("/orders")
public class OrderController {

    private final OrderRepository repository;

    @GetMapping
    public ResponseEntity find(OrderSpec spec) {

        Page<Order> page = repository.findAll(SpecBuilder.joinUser(spec));

        return ResponseEntity.ok(page);
    }
}

First convert current specification to Predicate object, then combine with new one (just like in SpecBuilder).

samie820 commented 4 years ago

Oh wow...thanks a lot, I ended up making a custom implementation that looks something like that, thanks again

m-cakir commented 4 years ago

I'm glad to hear that.

Don't forget to close the issue :D