Skip to content

Commit a81e57c

Browse files
authored
Merge pull request #246 from bcgov/feature/gradeSplit
Added district history pagination
2 parents 3a4d683 + e4801c1 commit a81e57c

File tree

6 files changed

+279
-30
lines changed

6 files changed

+279
-30
lines changed

api/src/main/java/ca/bc/gov/educ/api/institute/controller/v1/DistrictAPIController.java

Lines changed: 12 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -11,11 +11,8 @@
1111
import ca.bc.gov.educ.api.institute.messaging.jetstream.Publisher;
1212
import ca.bc.gov.educ.api.institute.model.v1.DistrictContactTombstoneEntity;
1313
import ca.bc.gov.educ.api.institute.model.v1.DistrictEntity;
14-
import ca.bc.gov.educ.api.institute.model.v1.InstituteEvent;
15-
import ca.bc.gov.educ.api.institute.service.v1.DistrictContactSearchService;
16-
import ca.bc.gov.educ.api.institute.service.v1.DistrictHistoryService;
17-
import ca.bc.gov.educ.api.institute.service.v1.DistrictSearchService;
18-
import ca.bc.gov.educ.api.institute.service.v1.DistrictService;
14+
import ca.bc.gov.educ.api.institute.model.v1.DistrictHistoryEntity;
15+
import ca.bc.gov.educ.api.institute.service.v1.*;
1916
import ca.bc.gov.educ.api.institute.struct.v1.District;
2017
import ca.bc.gov.educ.api.institute.struct.v1.DistrictContact;
2118
import ca.bc.gov.educ.api.institute.struct.v1.DistrictHistory;
@@ -63,6 +60,7 @@ public class DistrictAPIController implements DistrictAPIEndpoint {
6360
private static final NoteMapper noteMapper = NoteMapper.mapper;
6461
private final DistrictService districtService;
6562
private final DistrictSearchService districtSearchService;
63+
private final DistrictHistorySearchService districtHistorySearchService;
6664
private final DistrictContactSearchService districtContactSearchService;
6765
private final DistrictHistoryService districtHistoryService;
6866

@@ -73,10 +71,11 @@ public class DistrictAPIController implements DistrictAPIEndpoint {
7371
private final NotePayloadValidator notePayloadValidator;
7472

7573
@Autowired
76-
public DistrictAPIController(Publisher publisher, final DistrictService districtService, DistrictSearchService districtSearchService, DistrictContactSearchService districtContactSearchService, final DistrictHistoryService districtHistoryService, final DistrictPayloadValidator districtPayloadValidator, DistrictContactPayloadValidator districtContactPayloadValidator, NotePayloadValidator notePayloadValidator) {
74+
public DistrictAPIController(Publisher publisher, final DistrictService districtService, DistrictSearchService districtSearchService, DistrictHistorySearchService districtHistorySearchService, DistrictContactSearchService districtContactSearchService, final DistrictHistoryService districtHistoryService, final DistrictPayloadValidator districtPayloadValidator, DistrictContactPayloadValidator districtContactPayloadValidator, NotePayloadValidator notePayloadValidator) {
7775
this.publisher = publisher;
7876
this.districtService = districtService;
7977
this.districtSearchService = districtSearchService;
78+
this.districtHistorySearchService = districtHistorySearchService;
8079
this.districtContactSearchService = districtContactSearchService;
8180
this.districtHistoryService = districtHistoryService;
8281
this.districtPayloadValidator = districtPayloadValidator;
@@ -146,6 +145,13 @@ public CompletableFuture<Page<District>> findAll(Integer pageNumber, Integer pag
146145
return this.districtSearchService.findAll(districtSpecs, pageNumber, pageSize, sorts).thenApplyAsync(districtEntities -> districtEntities.map(mapper::toStructure));
147146
}
148147

148+
@Override
149+
public CompletableFuture<Page<DistrictHistory>> districtHistoryFindAll(Integer pageNumber, Integer pageSize, String sortCriteriaJson, String searchCriteriaListJson) {
150+
final List<Sort.Order> sorts = new ArrayList<>();
151+
Specification<DistrictHistoryEntity> districtHistorySpecs = districtHistorySearchService.setSpecificationAndSortCriteria(sortCriteriaJson, searchCriteriaListJson, JsonUtil.mapper, sorts);
152+
return this.districtHistorySearchService.findAll(districtHistorySpecs, pageNumber, pageSize, sorts).thenApplyAsync(districtHistoryEntities -> districtHistoryEntities.map(mapper::toStructure));
153+
}
154+
149155
@Override
150156
public CompletableFuture<Page<DistrictContact>> findAllContacts(Integer pageNumber, Integer pageSize, String sortCriteriaJson, String searchCriteriaListJson) {
151157
final List<Sort.Order> sorts = new ArrayList<>();

api/src/main/java/ca/bc/gov/educ/api/institute/endpoint/v1/DistrictAPIEndpoint.java

Lines changed: 11 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -80,6 +80,17 @@ CompletableFuture<Page<District>> findAll(@RequestParam(name = "pageNumber", def
8080
@RequestParam(name = "sort", defaultValue = "") String sortCriteriaJson,
8181
@RequestParam(name = "searchCriteriaList", required = false) String searchCriteriaListJson);
8282

83+
@GetMapping("/history/paginated")
84+
@Async
85+
@PreAuthorize("hasAuthority('SCOPE_READ_DISTRICT')")
86+
@ApiResponses(value = {@ApiResponse(responseCode = "200", description = "OK"), @ApiResponse(responseCode = "500", description = "INTERNAL SERVER ERROR.")})
87+
@Transactional(readOnly = true)
88+
@Tag(name = "District History Entity", description = "Endpoints for district history entity.")
89+
CompletableFuture<Page<DistrictHistory>> districtHistoryFindAll(@RequestParam(name = "pageNumber", defaultValue = "0") Integer pageNumber,
90+
@RequestParam(name = "pageSize", defaultValue = "10") Integer pageSize,
91+
@RequestParam(name = "sort", defaultValue = "") String sortCriteriaJson,
92+
@RequestParam(name = "searchCriteriaList", required = false) String searchCriteriaListJson);
93+
8394
@GetMapping("/contact/paginated")
8495
@Async
8596
@PreAuthorize("hasAuthority('SCOPE_READ_DISTRICT_CONTACT')")
Lines changed: 18 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,18 @@
1+
package ca.bc.gov.educ.api.institute.filter;
2+
3+
import ca.bc.gov.educ.api.institute.model.v1.DistrictHistoryEntity;
4+
import lombok.extern.slf4j.Slf4j;
5+
import org.springframework.stereotype.Service;
6+
7+
import java.time.chrono.ChronoLocalDate;
8+
import java.time.chrono.ChronoLocalDateTime;
9+
import java.util.UUID;
10+
11+
@Service
12+
@Slf4j
13+
public class DistrictHistoryFilterSpecs extends BaseFilterSpecs<DistrictHistoryEntity> {
14+
15+
public DistrictHistoryFilterSpecs(FilterSpecifications<DistrictHistoryEntity, ChronoLocalDate> dateFilterSpecifications, FilterSpecifications<DistrictHistoryEntity, ChronoLocalDateTime<?>> dateTimeFilterSpecifications, FilterSpecifications<DistrictHistoryEntity, Integer> integerFilterSpecifications, FilterSpecifications<DistrictHistoryEntity, String> stringFilterSpecifications, FilterSpecifications<DistrictHistoryEntity, Long> longFilterSpecifications, FilterSpecifications<DistrictHistoryEntity, UUID> uuidFilterSpecifications, FilterSpecifications<DistrictHistoryEntity, Boolean> booleanFilterSpecifications, Converters converters) {
16+
super(dateFilterSpecifications, dateTimeFilterSpecifications, integerFilterSpecifications, stringFilterSpecifications, longFilterSpecifications, uuidFilterSpecifications, booleanFilterSpecifications, converters);
17+
}
18+
}
Lines changed: 196 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,196 @@
1+
package ca.bc.gov.educ.api.institute.service.v1;
2+
3+
import ca.bc.gov.educ.api.institute.exception.InvalidParameterException;
4+
import ca.bc.gov.educ.api.institute.exception.SchoolAPIRuntimeException;
5+
import ca.bc.gov.educ.api.institute.filter.DistrictHistoryFilterSpecs;
6+
import ca.bc.gov.educ.api.institute.filter.FilterOperation;
7+
import ca.bc.gov.educ.api.institute.model.v1.DistrictHistoryEntity;
8+
import ca.bc.gov.educ.api.institute.model.v1.SchoolEntity;
9+
import ca.bc.gov.educ.api.institute.repository.v1.DistrictHistoryRepository;
10+
import ca.bc.gov.educ.api.institute.struct.v1.Condition;
11+
import ca.bc.gov.educ.api.institute.struct.v1.Search;
12+
import ca.bc.gov.educ.api.institute.struct.v1.SearchCriteria;
13+
import ca.bc.gov.educ.api.institute.struct.v1.ValueType;
14+
import ca.bc.gov.educ.api.institute.util.RequestUtil;
15+
import ca.bc.gov.educ.api.institute.util.TransformUtil;
16+
import com.fasterxml.jackson.core.JsonProcessingException;
17+
import com.fasterxml.jackson.core.type.TypeReference;
18+
import com.fasterxml.jackson.databind.ObjectMapper;
19+
import com.google.common.util.concurrent.ThreadFactoryBuilder;
20+
import lombok.extern.slf4j.Slf4j;
21+
import org.apache.commons.lang3.StringUtils;
22+
import org.jboss.threads.EnhancedQueueExecutor;
23+
import org.springframework.data.domain.Page;
24+
import org.springframework.data.domain.PageRequest;
25+
import org.springframework.data.domain.Pageable;
26+
import org.springframework.data.domain.Sort;
27+
import org.springframework.data.jpa.domain.Specification;
28+
import org.springframework.stereotype.Service;
29+
import org.springframework.transaction.annotation.Propagation;
30+
import org.springframework.transaction.annotation.Transactional;
31+
32+
import java.time.Duration;
33+
import java.util.List;
34+
import java.util.concurrent.CompletableFuture;
35+
import java.util.concurrent.CompletionException;
36+
import java.util.concurrent.Executor;
37+
38+
/**
39+
* The type School search service.
40+
*/
41+
@Service
42+
@Slf4j
43+
public class DistrictHistorySearchService {
44+
private final DistrictHistoryFilterSpecs districtHistoryFilterSpecs;
45+
46+
private final DistrictHistoryRepository districtHistoryRepository;
47+
48+
private final Executor paginatedQueryExecutor = new EnhancedQueueExecutor.Builder()
49+
.setThreadFactory(new ThreadFactoryBuilder().setNameFormat("async-pagination-query-executor-%d").build())
50+
.setCorePoolSize(2).setMaximumPoolSize(10).setKeepAliveTime(Duration.ofSeconds(60)).build();
51+
52+
public DistrictHistorySearchService(DistrictHistoryFilterSpecs districtHistoryFilterSpecs, DistrictHistoryRepository districtHistoryRepository) {
53+
this.districtHistoryFilterSpecs = districtHistoryFilterSpecs;
54+
this.districtHistoryRepository = districtHistoryRepository;
55+
}
56+
57+
/**
58+
* Gets specifications.
59+
*
60+
* @param districtSpecs the pen reg batch specs
61+
* @param i the
62+
* @param search the search
63+
* @return the specifications
64+
*/
65+
public Specification<DistrictHistoryEntity> getSpecifications(Specification<DistrictHistoryEntity> districtSpecs, int i, Search search) {
66+
if (i == 0) {
67+
districtSpecs = getDistrictHistoryEntitySpecification(search.getSearchCriteriaList());
68+
} else {
69+
if (search.getCondition() == Condition.AND) {
70+
districtSpecs = districtSpecs.and(getDistrictHistoryEntitySpecification(search.getSearchCriteriaList()));
71+
} else {
72+
districtSpecs = districtSpecs.or(getDistrictHistoryEntitySpecification(search.getSearchCriteriaList()));
73+
}
74+
}
75+
return districtSpecs;
76+
}
77+
78+
private Specification<DistrictHistoryEntity> getDistrictHistoryEntitySpecification(List<SearchCriteria> criteriaList) {
79+
Specification<DistrictHistoryEntity> districtSpecs = null;
80+
if (!criteriaList.isEmpty()) {
81+
int i = 0;
82+
for (SearchCriteria criteria : criteriaList) {
83+
if (criteria.getKey() != null && criteria.getOperation() != null && criteria.getValueType() != null) {
84+
var criteriaValue = criteria.getValue();
85+
if(StringUtils.isNotBlank(criteria.getValue()) && TransformUtil.isUppercaseField(SchoolEntity.class, criteria.getKey())) {
86+
criteriaValue = criteriaValue.toUpperCase();
87+
}
88+
Specification<DistrictHistoryEntity> typeSpecification = getTypeSpecification(criteria.getKey(), criteria.getOperation(), criteriaValue, criteria.getValueType());
89+
districtSpecs = getSpecificationPerGroup(districtSpecs, i, criteria, typeSpecification);
90+
i++;
91+
} else {
92+
throw new InvalidParameterException("Search Criteria can not contain null values for key, value and operation type");
93+
}
94+
}
95+
}
96+
return districtSpecs;
97+
}
98+
99+
/**
100+
* Gets specification per group.
101+
*
102+
* @param districtHistoryEntitySpecification the pen request batch entity specification
103+
* @param i the
104+
* @param criteria the criteria
105+
* @param typeSpecification the type specification
106+
* @return the specification per group
107+
*/
108+
private Specification<DistrictHistoryEntity> getSpecificationPerGroup(Specification<DistrictHistoryEntity> districtHistoryEntitySpecification, int i, SearchCriteria criteria, Specification<DistrictHistoryEntity> typeSpecification) {
109+
if (i == 0) {
110+
districtHistoryEntitySpecification = Specification.where(typeSpecification);
111+
} else {
112+
if (criteria.getCondition() == Condition.AND) {
113+
districtHistoryEntitySpecification = districtHistoryEntitySpecification.and(typeSpecification);
114+
} else {
115+
districtHistoryEntitySpecification = districtHistoryEntitySpecification.or(typeSpecification);
116+
}
117+
}
118+
return districtHistoryEntitySpecification;
119+
}
120+
121+
private Specification<DistrictHistoryEntity> getTypeSpecification(String key, FilterOperation filterOperation, String value, ValueType valueType) {
122+
Specification<DistrictHistoryEntity> schoolEntitySpecification = null;
123+
switch (valueType) {
124+
case STRING:
125+
schoolEntitySpecification = districtHistoryFilterSpecs.getStringTypeSpecification(key, value, filterOperation);
126+
break;
127+
case DATE_TIME:
128+
schoolEntitySpecification = districtHistoryFilterSpecs.getDateTimeTypeSpecification(key, value, filterOperation);
129+
break;
130+
case LONG:
131+
schoolEntitySpecification = districtHistoryFilterSpecs.getLongTypeSpecification(key, value, filterOperation);
132+
break;
133+
case INTEGER:
134+
schoolEntitySpecification = districtHistoryFilterSpecs.getIntegerTypeSpecification(key, value, filterOperation);
135+
break;
136+
case DATE:
137+
schoolEntitySpecification = districtHistoryFilterSpecs.getDateTypeSpecification(key, value, filterOperation);
138+
break;
139+
case UUID:
140+
schoolEntitySpecification = districtHistoryFilterSpecs.getUUIDTypeSpecification(key, value, filterOperation);
141+
break;
142+
case BOOLEAN:
143+
schoolEntitySpecification = districtHistoryFilterSpecs.getBooleanTypeSpecification(key, value, filterOperation);
144+
break;
145+
default:
146+
break;
147+
}
148+
return schoolEntitySpecification;
149+
}
150+
151+
@Transactional(propagation = Propagation.SUPPORTS)
152+
public CompletableFuture<Page<DistrictHistoryEntity>> findAll(Specification<DistrictHistoryEntity> districtSpecs, final Integer pageNumber, final Integer pageSize, final List<Sort.Order> sorts) {
153+
log.trace("In find all query: {}", districtSpecs);
154+
return CompletableFuture.supplyAsync(() -> {
155+
Pageable paging = PageRequest.of(pageNumber, pageSize, Sort.by(sorts));
156+
try {
157+
log.trace("Running paginated query: {}", districtSpecs);
158+
var results = this.districtHistoryRepository.findAll(districtSpecs, paging);
159+
log.trace("Paginated query returned with results: {}", results);
160+
return results;
161+
} catch (final Throwable ex) {
162+
log.error("Failure querying for paginated schools: {}", ex.getMessage());
163+
throw new CompletionException(ex);
164+
}
165+
}, paginatedQueryExecutor);
166+
167+
}
168+
169+
/**
170+
* Sets specification and sort criteria.
171+
*
172+
* @param sortCriteriaJson the sort criteria json
173+
* @param searchCriteriaListJson the search criteria list json
174+
* @param objectMapper the object mapper
175+
* @param sorts the sorts
176+
* @return the specification and sort criteria
177+
*/
178+
public Specification<DistrictHistoryEntity> setSpecificationAndSortCriteria(String sortCriteriaJson, String searchCriteriaListJson, ObjectMapper objectMapper, List<Sort.Order> sorts) {
179+
Specification<DistrictHistoryEntity> districtSpecs = null;
180+
try {
181+
RequestUtil.getSortCriteria(sortCriteriaJson, objectMapper, sorts);
182+
if (StringUtils.isNotBlank(searchCriteriaListJson)) {
183+
List<Search> searches = objectMapper.readValue(searchCriteriaListJson, new TypeReference<>() {
184+
});
185+
int i = 0;
186+
for (var search : searches) {
187+
districtSpecs = getSpecifications(districtSpecs, i, search);
188+
i++;
189+
}
190+
}
191+
} catch (JsonProcessingException e) {
192+
throw new SchoolAPIRuntimeException(e.getMessage());
193+
}
194+
return districtSpecs;
195+
}
196+
}

0 commit comments

Comments
 (0)