FINERACT-1724: Final touches for Client V2 search API
This commit is contained in:
committed by
Arnold Gálovics
parent
68c6053bd7
commit
fd27401de4
+67
-3
@@ -19,23 +19,35 @@
|
|||||||
package org.apache.fineract.infrastructure.core.jpa;
|
package org.apache.fineract.infrastructure.core.jpa;
|
||||||
|
|
||||||
import java.util.ArrayList;
|
import java.util.ArrayList;
|
||||||
|
import java.util.Collections;
|
||||||
import java.util.List;
|
import java.util.List;
|
||||||
import java.util.function.Supplier;
|
import java.util.function.Supplier;
|
||||||
|
import javax.persistence.EntityManager;
|
||||||
|
import javax.persistence.TypedQuery;
|
||||||
import javax.persistence.criteria.CriteriaBuilder;
|
import javax.persistence.criteria.CriteriaBuilder;
|
||||||
|
import javax.persistence.criteria.CriteriaQuery;
|
||||||
import javax.persistence.criteria.Order;
|
import javax.persistence.criteria.Order;
|
||||||
|
import javax.persistence.criteria.Predicate;
|
||||||
import javax.persistence.criteria.Root;
|
import javax.persistence.criteria.Root;
|
||||||
|
import lombok.RequiredArgsConstructor;
|
||||||
|
import org.springframework.data.domain.Page;
|
||||||
import org.springframework.data.domain.Pageable;
|
import org.springframework.data.domain.Pageable;
|
||||||
import org.springframework.data.domain.Sort;
|
import org.springframework.data.domain.Sort;
|
||||||
|
import org.springframework.data.jpa.domain.Specification;
|
||||||
|
import org.springframework.data.support.PageableExecutionUtils;
|
||||||
import org.springframework.stereotype.Component;
|
import org.springframework.stereotype.Component;
|
||||||
|
|
||||||
@Component
|
@Component
|
||||||
|
@RequiredArgsConstructor
|
||||||
public class CriteriaQueryFactory {
|
public class CriteriaQueryFactory {
|
||||||
|
|
||||||
public List<Order> fromPageable(Pageable pageable, CriteriaBuilder cb, Root<?> root) {
|
private final EntityManager entityManager;
|
||||||
return fromPageable(pageable, cb, root, () -> null);
|
|
||||||
|
public List<Order> ordersFromPageable(Pageable pageable, CriteriaBuilder cb, Root<?> root) {
|
||||||
|
return ordersFromPageable(pageable, cb, root, () -> null);
|
||||||
}
|
}
|
||||||
|
|
||||||
public List<Order> fromPageable(Pageable pageable, CriteriaBuilder cb, Root<?> root, Supplier<Order> defaultOrderSupplier) {
|
public List<Order> ordersFromPageable(Pageable pageable, CriteriaBuilder cb, Root<?> root, Supplier<Order> defaultOrderSupplier) {
|
||||||
List<Order> orders = new ArrayList<>();
|
List<Order> orders = new ArrayList<>();
|
||||||
Sort sort = pageable.getSort();
|
Sort sort = pageable.getSort();
|
||||||
if (sort.isSorted()) {
|
if (sort.isSorted()) {
|
||||||
@@ -54,4 +66,56 @@ public class CriteriaQueryFactory {
|
|||||||
}
|
}
|
||||||
return orders;
|
return orders;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public <S, U> Page<S> readPage(TypedQuery<S> query, Class<U> domainClass, Pageable pageable, Specification<U> spec) {
|
||||||
|
|
||||||
|
if (pageable.isPaged()) {
|
||||||
|
query.setFirstResult((int) pageable.getOffset());
|
||||||
|
query.setMaxResults(pageable.getPageSize());
|
||||||
|
}
|
||||||
|
|
||||||
|
return PageableExecutionUtils.getPage(query.getResultList(), pageable, () -> executeCountQuery(getCountQuery(spec, domainClass)));
|
||||||
|
}
|
||||||
|
|
||||||
|
private static long executeCountQuery(TypedQuery<Long> query) {
|
||||||
|
List<Long> totals = query.getResultList();
|
||||||
|
long total = 0L;
|
||||||
|
|
||||||
|
for (Long element : totals) {
|
||||||
|
total += element == null ? 0 : element;
|
||||||
|
}
|
||||||
|
return total;
|
||||||
|
}
|
||||||
|
|
||||||
|
private <S> TypedQuery<Long> getCountQuery(Specification<S> spec, Class<S> domainClass) {
|
||||||
|
CriteriaBuilder builder = entityManager.getCriteriaBuilder();
|
||||||
|
CriteriaQuery<Long> query = builder.createQuery(Long.class);
|
||||||
|
|
||||||
|
Root<S> root = query.from(domainClass);
|
||||||
|
|
||||||
|
applySpecificationToCriteria(root, spec, query);
|
||||||
|
|
||||||
|
if (query.isDistinct()) {
|
||||||
|
query.select(builder.countDistinct(root));
|
||||||
|
} else {
|
||||||
|
query.select(builder.count(root));
|
||||||
|
}
|
||||||
|
|
||||||
|
// Remove all Orders the Specifications might have applied
|
||||||
|
query.orderBy(Collections.emptyList());
|
||||||
|
|
||||||
|
return entityManager.createQuery(query);
|
||||||
|
}
|
||||||
|
|
||||||
|
public <S, U> Root<U> applySpecificationToCriteria(Root<U> root, Specification<U> spec, CriteriaQuery<S> query) {
|
||||||
|
|
||||||
|
CriteriaBuilder builder = entityManager.getCriteriaBuilder();
|
||||||
|
Predicate predicate = spec.toPredicate(root, query, builder);
|
||||||
|
|
||||||
|
if (predicate != null) {
|
||||||
|
query.where(predicate);
|
||||||
|
}
|
||||||
|
|
||||||
|
return root;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
+1
-1
@@ -31,7 +31,7 @@ public class SearchedClient {
|
|||||||
private final Long id;
|
private final Long id;
|
||||||
private final String displayName;
|
private final String displayName;
|
||||||
private final ExternalId externalId;
|
private final ExternalId externalId;
|
||||||
private final String accountNo;
|
private final String accountNumber;
|
||||||
private final Long officeId;
|
private final Long officeId;
|
||||||
private final String officeName;
|
private final String officeName;
|
||||||
private final String mobileNo;
|
private final String mobileNo;
|
||||||
|
|||||||
+22
-16
@@ -21,6 +21,7 @@ package org.apache.fineract.portfolio.client.domain.search;
|
|||||||
import java.util.ArrayList;
|
import java.util.ArrayList;
|
||||||
import java.util.List;
|
import java.util.List;
|
||||||
import javax.persistence.EntityManager;
|
import javax.persistence.EntityManager;
|
||||||
|
import javax.persistence.TypedQuery;
|
||||||
import javax.persistence.criteria.CriteriaBuilder;
|
import javax.persistence.criteria.CriteriaBuilder;
|
||||||
import javax.persistence.criteria.CriteriaQuery;
|
import javax.persistence.criteria.CriteriaQuery;
|
||||||
import javax.persistence.criteria.Order;
|
import javax.persistence.criteria.Order;
|
||||||
@@ -32,8 +33,8 @@ import org.apache.fineract.infrastructure.core.jpa.CriteriaQueryFactory;
|
|||||||
import org.apache.fineract.organisation.office.domain.Office;
|
import org.apache.fineract.organisation.office.domain.Office;
|
||||||
import org.apache.fineract.portfolio.client.domain.Client;
|
import org.apache.fineract.portfolio.client.domain.Client;
|
||||||
import org.springframework.data.domain.Page;
|
import org.springframework.data.domain.Page;
|
||||||
import org.springframework.data.domain.PageImpl;
|
|
||||||
import org.springframework.data.domain.Pageable;
|
import org.springframework.data.domain.Pageable;
|
||||||
|
import org.springframework.data.jpa.domain.Specification;
|
||||||
import org.springframework.stereotype.Repository;
|
import org.springframework.stereotype.Repository;
|
||||||
|
|
||||||
@Repository
|
@Repository
|
||||||
@@ -55,28 +56,33 @@ public class SearchingClientRepositoryImpl implements SearchingClientRepository
|
|||||||
|
|
||||||
CriteriaBuilder cb = entityManager.getCriteriaBuilder();
|
CriteriaBuilder cb = entityManager.getCriteriaBuilder();
|
||||||
CriteriaQuery<SearchedClient> query = cb.createQuery(SearchedClient.class);
|
CriteriaQuery<SearchedClient> query = cb.createQuery(SearchedClient.class);
|
||||||
|
|
||||||
Root<Client> root = query.from(Client.class);
|
Root<Client> root = query.from(Client.class);
|
||||||
Path<Office> office = root.get("office");
|
Path<Office> office = root.get("office");
|
||||||
|
|
||||||
|
Specification<Client> spec = (r, q, builder) -> {
|
||||||
|
Path<Office> o = r.get("office");
|
||||||
|
|
||||||
|
List<Predicate> predicates = new ArrayList<>();
|
||||||
|
predicates.add(cb.like(o.get("hierarchy"), hierarchyLikeValue));
|
||||||
|
|
||||||
|
String searchLikeValue = "%" + searchText + "%";
|
||||||
|
predicates.add(cb.or(cb.like(r.get("accountNumber"), searchLikeValue), cb.like(r.get("displayName"), searchLikeValue),
|
||||||
|
cb.like(r.get("externalId"), searchLikeValue), cb.like(r.get("mobileNo"), searchLikeValue)));
|
||||||
|
|
||||||
|
return cb.and(predicates.toArray(new Predicate[0]));
|
||||||
|
};
|
||||||
|
criteriaQueryFactory.applySpecificationToCriteria(root, spec, query);
|
||||||
|
|
||||||
|
List<Order> orders = criteriaQueryFactory.ordersFromPageable(pageable, cb, root, () -> cb.desc(root.get("id")));
|
||||||
|
query.orderBy(orders);
|
||||||
|
|
||||||
query.select(cb.construct(SearchedClient.class, root.get("id"), root.get("displayName"), root.get("externalId"),
|
query.select(cb.construct(SearchedClient.class, root.get("id"), root.get("displayName"), root.get("externalId"),
|
||||||
root.get("accountNumber"), office.get("id"), office.get("name"), root.get("mobileNo"), root.get("status"),
|
root.get("accountNumber"), office.get("id"), office.get("name"), root.get("mobileNo"), root.get("status"),
|
||||||
root.get("activationDate"), root.get("createdDate")));
|
root.get("activationDate"), root.get("createdDate")));
|
||||||
|
|
||||||
List<Predicate> predicates = new ArrayList<>();
|
TypedQuery<SearchedClient> queryToExecute = entityManager.createQuery(query);
|
||||||
predicates.add(cb.like(office.get("hierarchy"), hierarchyLikeValue));
|
|
||||||
|
|
||||||
String searchLikeValue = "%" + searchText + "%";
|
return criteriaQueryFactory.readPage(queryToExecute, Client.class, pageable, spec);
|
||||||
predicates.add(cb.or(cb.like(root.get("accountNumber"), searchLikeValue), cb.like(root.get("displayName"), searchLikeValue),
|
|
||||||
cb.like(root.get("externalId"), searchLikeValue), cb.like(root.get("mobileNo"), searchLikeValue)));
|
|
||||||
|
|
||||||
query.where(cb.and(predicates.toArray(new Predicate[0])));
|
|
||||||
|
|
||||||
List<Order> orders = criteriaQueryFactory.fromPageable(pageable, cb, root, () -> cb.desc(root.get("id")));
|
|
||||||
query.orderBy(orders);
|
|
||||||
|
|
||||||
List<SearchedClient> result = entityManager.createQuery(query).setFirstResult(pageable.getPageNumber())
|
|
||||||
.setMaxResults(pageable.getPageSize()).getResultList();
|
|
||||||
|
|
||||||
return new PageImpl<>(result, pageable, result.size());
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
+1
-1
@@ -30,7 +30,7 @@ public class ClientSearchData {
|
|||||||
private Long id;
|
private Long id;
|
||||||
private String displayName;
|
private String displayName;
|
||||||
private ExternalId externalId;
|
private ExternalId externalId;
|
||||||
private String accountNo;
|
private String accountNumber;
|
||||||
private Long officeId;
|
private Long officeId;
|
||||||
private String officeName;
|
private String officeName;
|
||||||
private String mobileNo;
|
private String mobileNo;
|
||||||
|
|||||||
+24
-1
@@ -50,6 +50,29 @@ public class ClientSearchTest {
|
|||||||
clientHelper = new ClientHelper(requestSpec, responseSpec);
|
clientHelper = new ClientHelper(requestSpec, responseSpec);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
public void testClientSearchWorks_WithLastnameText_WithPaging() {
|
||||||
|
// given
|
||||||
|
String lastname = Utils.randomStringGenerator("Client_LastName_", 5);
|
||||||
|
PostClientsRequest request1 = ClientHelper.defaultClientCreationRequest();
|
||||||
|
request1.setLastname(lastname);
|
||||||
|
clientHelper.createClient(request1);
|
||||||
|
|
||||||
|
PostClientsRequest request2 = ClientHelper.defaultClientCreationRequest();
|
||||||
|
request2.setLastname(lastname);
|
||||||
|
clientHelper.createClient(request2);
|
||||||
|
|
||||||
|
PostClientsRequest request3 = ClientHelper.defaultClientCreationRequest();
|
||||||
|
request3.setLastname(lastname);
|
||||||
|
clientHelper.createClient(request3);
|
||||||
|
// when
|
||||||
|
PageClientSearchData result = clientHelper.searchClients(lastname, 0, 1);
|
||||||
|
// then
|
||||||
|
assertThat(result.getTotalElements()).isEqualTo(3);
|
||||||
|
assertThat(result.getNumberOfElements()).isEqualTo(1);
|
||||||
|
assertThat(result.getTotalPages()).isEqualTo(3);
|
||||||
|
}
|
||||||
|
|
||||||
@Test
|
@Test
|
||||||
public void testClientSearchWorks_WithLastnameTextOnDefaultOrdering() {
|
public void testClientSearchWorks_WithLastnameTextOnDefaultOrdering() {
|
||||||
// given
|
// given
|
||||||
@@ -135,7 +158,7 @@ public class ClientSearchTest {
|
|||||||
PageClientSearchData result = clientHelper.searchClients(client2Data.getAccountNo());
|
PageClientSearchData result = clientHelper.searchClients(client2Data.getAccountNo());
|
||||||
// then
|
// then
|
||||||
assertThat(result.getTotalElements()).isEqualTo(1);
|
assertThat(result.getTotalElements()).isEqualTo(1);
|
||||||
assertThat(result.getContent().get(0).getAccountNo()).isEqualTo(client2Data.getAccountNo());
|
assertThat(result.getContent().get(0).getAccountNumber()).isEqualTo(client2Data.getAccountNo());
|
||||||
}
|
}
|
||||||
|
|
||||||
@Test
|
@Test
|
||||||
|
|||||||
+10
@@ -109,6 +109,16 @@ public class ClientHelper extends IntegrationTest {
|
|||||||
return searchClients(request);
|
return searchClients(request);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public PageClientSearchData searchClients(String text, int page, int pageSize) {
|
||||||
|
ClientTextSearch clientTextSearch = new ClientTextSearch();
|
||||||
|
clientTextSearch.setText(text);
|
||||||
|
PagedRequestClientTextSearch request = new PagedRequestClientTextSearch();
|
||||||
|
request.setRequest(clientTextSearch);
|
||||||
|
request.setPage(page);
|
||||||
|
request.setSize(pageSize);
|
||||||
|
return searchClients(request);
|
||||||
|
}
|
||||||
|
|
||||||
public PageClientSearchData searchClients(String text, SortOrder sortOrder) {
|
public PageClientSearchData searchClients(String text, SortOrder sortOrder) {
|
||||||
ClientTextSearch clientTextSearch = new ClientTextSearch();
|
ClientTextSearch clientTextSearch = new ClientTextSearch();
|
||||||
clientTextSearch.setText(text);
|
clientTextSearch.setText(text);
|
||||||
|
|||||||
Reference in New Issue
Block a user