Skip to content

Commit d3395fe

Browse files
Sorting and filtering in shortcircuit results (#77)
Signed-off-by: Mathieu DEHARBE <[email protected]>
1 parent c21c339 commit d3395fe

14 files changed

+405
-64
lines changed

src/main/java/org/gridsuite/shortcircuit/server/entities/FaultResultEntity.java

Lines changed: 12 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -10,6 +10,7 @@
1010
import lombok.Getter;
1111
import lombok.NoArgsConstructor;
1212
import lombok.Setter;
13+
import lombok.experimental.FieldNameConstants;
1314

1415
import java.util.List;
1516
import java.util.UUID;
@@ -19,6 +20,7 @@
1920
*/
2021
@Getter
2122
@Setter
23+
@FieldNameConstants
2224
@NoArgsConstructor
2325
@Entity
2426
@Table(indexes = {
@@ -54,6 +56,13 @@ public class FaultResultEntity {
5456
columnList = "fault_result_entity_fault_result_uuid")})
5557
private List<LimitViolationEmbeddable> limitViolations;
5658

59+
/**
60+
* first limit violation of the fault result entity, from 'limitViolations'
61+
* Must save it inside the same table in order to get pageable FaultResultEntity sortable by this firstLimitViolation data
62+
*/
63+
@Embedded
64+
private LimitViolationEmbeddable firstLimitViolation;
65+
5766
/*
5867
Bidirectional relation is not needed here and is done for performance
5968
https://vladmihalcea.com/the-best-way-to-map-a-onetomany-association-with-jpa-and-hibernate/
@@ -114,6 +123,9 @@ public FaultResultEntity(FaultEmbeddable fault, double current, double shortCirc
114123
if (limitViolations != null) {
115124
this.limitViolations = limitViolations;
116125
this.nbLimitViolations = limitViolations.size();
126+
if (!this.limitViolations.isEmpty()) {
127+
this.firstLimitViolation = this.limitViolations.get(0);
128+
}
117129
}
118130
this.ipMin = ipMin;
119131
this.ipMax = ipMax;

src/main/java/org/gridsuite/shortcircuit/server/entities/FeederResultEntity.java

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -10,6 +10,7 @@
1010
import lombok.NoArgsConstructor;
1111

1212
import jakarta.persistence.*;
13+
import lombok.experimental.FieldNameConstants;
1314

1415
import java.util.UUID;
1516

@@ -19,6 +20,7 @@
1920
@Getter
2021
@NoArgsConstructor
2122
@Entity
23+
@FieldNameConstants
2224
@Table(name = "feeder_results",
2325
indexes = {@Index(name = "feeder_results_fault_result_idx",
2426
columnList = "fault_result_entity_fault_result_uuid")})

src/main/java/org/gridsuite/shortcircuit/server/entities/LimitViolationEmbeddable.java

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -15,6 +15,7 @@
1515
import jakarta.persistence.Embeddable;
1616
import jakarta.persistence.EnumType;
1717
import jakarta.persistence.Enumerated;
18+
import lombok.experimental.FieldNameConstants;
1819

1920
/**
2021
* @author Nicolas Noir <nicolas.noir at rte-france.com>
@@ -23,6 +24,7 @@
2324
@AllArgsConstructor
2425
@NoArgsConstructor
2526
@Embeddable
27+
@FieldNameConstants
2628
public class LimitViolationEmbeddable {
2729

2830
@Column

src/main/java/org/gridsuite/shortcircuit/server/entities/ShortCircuitAnalysisResultEntity.java

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -11,6 +11,8 @@
1111
import lombok.Setter;
1212

1313
import jakarta.persistence.*;
14+
import lombok.experimental.FieldNameConstants;
15+
1416
import java.time.ZonedDateTime;
1517
import java.util.Set;
1618
import java.util.UUID;
@@ -19,6 +21,7 @@
1921
* @author Nicolas Noir <nicolas.noir at rte-france.com>
2022
*/
2123
@Getter
24+
@FieldNameConstants
2225
@NoArgsConstructor
2326
@Entity
2427
@Table(name = "shortcircuit_result")

src/main/java/org/gridsuite/shortcircuit/server/repositories/FaultResultRepository.java

Lines changed: 9 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -7,6 +7,7 @@
77
package org.gridsuite.shortcircuit.server.repositories;
88

99
import org.gridsuite.shortcircuit.server.entities.FaultResultEntity;
10+
import org.springframework.data.jpa.domain.Specification;
1011
import org.springframework.data.jpa.repository.*;
1112
import org.springframework.data.jpa.repository.EntityGraph.EntityGraphType;
1213
import org.springframework.stereotype.Repository;
@@ -24,6 +25,9 @@ public interface FaultResultRepository extends JpaRepository<FaultResultEntity,
2425
@EntityGraph(attributePaths = {"limitViolations"}, type = EntityGraphType.LOAD)
2526
Set<FaultResultEntity> findAllWithLimitViolationsByFaultResultUuidIn(List<UUID> faultResultsUUID);
2627

28+
@EntityGraph(attributePaths = {"feederResults"}, type = EntityGraphType.LOAD)
29+
List<FaultResultEntity> findAll(Specification<FaultResultEntity> specification);
30+
2731
@EntityGraph(attributePaths = {"feederResults"}, type = EntityGraphType.LOAD)
2832
Set<FaultResultEntity> findAllWithFeederResultsByFaultResultUuidIn(List<UUID> faultResultsUUID);
2933

@@ -46,4 +50,9 @@ public interface FaultResultRepository extends JpaRepository<FaultResultEntity,
4650
@Query(value = "DELETE FROM feeder_results WHERE fault_result_entity_fault_result_uuid IN ?1", nativeQuery = true)
4751
void deleteFeederResultsByFaultResultUuids(Set<UUID> ids);
4852

53+
List<FaultResultEntity> findAllByFaultResultUuidIn(List<UUID> uuids);
54+
55+
interface EntityId {
56+
UUID getFaultResultUuid();
57+
}
4958
}

src/main/java/org/gridsuite/shortcircuit/server/repositories/ShortCircuitAnalysisResultRepository.java

Lines changed: 116 additions & 18 deletions
Original file line numberDiff line numberDiff line change
@@ -12,9 +12,9 @@
1212
import org.gridsuite.shortcircuit.server.dto.ResourceFilter;
1313
import org.gridsuite.shortcircuit.server.dto.ShortCircuitLimits;
1414
import org.gridsuite.shortcircuit.server.entities.*;
15+
import org.gridsuite.shortcircuit.server.repositories.specifications.FaultResultSpecificationBuilder;
1516
import org.gridsuite.shortcircuit.server.service.ShortCircuitRunContext;
16-
import org.gridsuite.shortcircuit.server.utils.FaultResultSpecificationBuilder;
17-
import org.gridsuite.shortcircuit.server.utils.FeederResultSpecificationBuilder;
17+
import org.gridsuite.shortcircuit.server.repositories.specifications.FeederResultSpecificationBuilder;
1818
import org.slf4j.Logger;
1919
import org.slf4j.LoggerFactory;
2020
import org.springframework.beans.factory.annotation.Autowired;
@@ -65,7 +65,7 @@ private static List<LimitViolationEmbeddable> extractLimitViolations(FaultResult
6565
.map(limitViolation -> new LimitViolationEmbeddable(limitViolation.getSubjectId(),
6666
limitViolation.getLimitType(), limitViolation.getLimit(),
6767
limitViolation.getLimitName(), limitViolation.getValue()))
68-
.collect(Collectors.toList());
68+
.toList();
6969
}
7070

7171
public static ShortCircuitAnalysisResultEntity toResultEntity(UUID resultUuid, ShortCircuitAnalysisResult result, Map<String, ShortCircuitLimits> allShortCircuitLimits) {
@@ -116,7 +116,7 @@ private static FaultResultEntity toMagnitudeFaultResultEntity(MagnitudeFaultResu
116116
entity.setFeederResults(faultResult.getFeederResults().stream()
117117
.map(feederResult -> new FeederResultEntity(feederResult.getConnectableId(),
118118
((MagnitudeFeederResult) feederResult).getCurrent(), null))
119-
.collect(Collectors.toList()));
119+
.toList());
120120
if (shortCircuitLimits != null) {
121121
entity.setDeltaCurrentIpMin(current - entity.getIpMin() / 1000.0);
122122
entity.setDeltaCurrentIpMax(current - entity.getIpMax() / 1000.0);
@@ -138,7 +138,7 @@ private static FaultResultEntity toFortescueFaultResultEntity(FortescueFaultResu
138138
feederFortescueThreePhaseValue.getMagnitudeC(), feederFortescueThreePhaseValue.getAngleA(),
139139
feederFortescueThreePhaseValue.getAngleB(), feederFortescueThreePhaseValue.getAngleC()));
140140
})
141-
.collect(Collectors.toList()));
141+
.toList());
142142

143143
final FortescueValue current = faultResult.getCurrent();
144144
if (shortCircuitLimits != null) {
@@ -163,7 +163,7 @@ private static GlobalStatusEntity toStatusEntity(UUID resultUuid, String status)
163163
public void insertStatus(List<UUID> resultUuids, String status) {
164164
Objects.requireNonNull(resultUuids);
165165
globalStatusRepository.saveAll(resultUuids.stream()
166-
.map(uuid -> toStatusEntity(uuid, status)).collect(Collectors.toList()));
166+
.map(uuid -> toStatusEntity(uuid, status)).toList());
167167
}
168168

169169
@Transactional
@@ -238,7 +238,7 @@ public Optional<ShortCircuitAnalysisResultEntity> findResultsWithLimitViolations
238238
List<UUID> faultResultsUuidWithLimitViolations = result.get().getFaultResults().stream()
239239
.filter(fr -> !fr.getLimitViolations().isEmpty())
240240
.map(FaultResultEntity::getFaultResultUuid)
241-
.collect(Collectors.toList());
241+
.toList();
242242
// using the Hibernate First-Level Cache or Persistence Context
243243
// cf.https://vladmihalcea.com/spring-data-jpa-multiplebagfetchexception/
244244
if (!result.get().getFaultResults().isEmpty()) {
@@ -248,20 +248,70 @@ public Optional<ShortCircuitAnalysisResultEntity> findResultsWithLimitViolations
248248
}
249249

250250
@Transactional(readOnly = true)
251-
public Page<FaultResultEntity> findFaultResultsPage(ShortCircuitAnalysisResultEntity result, List<ResourceFilter> resourceFilters, Pageable pageable, FaultResultsMode mode) {
251+
public Page<FaultResultEntity> findFaultResultsPage(ShortCircuitAnalysisResultEntity result,
252+
List<ResourceFilter> resourceFilters,
253+
Pageable pageable,
254+
FaultResultsMode mode) {
252255
Objects.requireNonNull(result);
256+
257+
Optional<Sort.Order> childrenSort = extractChildrenSort(pageable);
258+
259+
Pageable modifiedPageable = addDefaultSort(filterOutChildrenSort(pageable, childrenSort),
260+
DEFAULT_FAULT_RESULT_SORT_COLUMN);
253261
Specification<FaultResultEntity> specification = FaultResultSpecificationBuilder.buildSpecification(result.getResultUuid(), resourceFilters);
254262
// WARN org.hibernate.hql.internal.ast.QueryTranslatorImpl -
255263
// HHH000104: firstResult/maxResults specified with collection fetch; applying in memory!
256264
// cf. https://vladmihalcea.com/fix-hibernate-hhh000104-entity-fetch-pagination-warning-message/
257265
// We must separate in two requests, one with pagination the other one with Join Fetch
258-
Page<FaultResultEntity> faultResultsPage = faultResultRepository.findAll(specification, addDefaultSort(pageable, DEFAULT_FAULT_RESULT_SORT_COLUMN));
259-
if (faultResultsPage.hasContent() && mode != FaultResultsMode.BASIC) {
260-
appendLimitViolationsAndFeederResults(faultResultsPage);
266+
267+
Page<FaultResultRepository.EntityId> uuidPage = faultResultRepository.findBy(specification, q ->
268+
q.project(FaultResultEntity.Fields.faultResultUuid)
269+
.as(FaultResultRepository.EntityId.class)
270+
.sortBy(modifiedPageable.getSort())
271+
.page(modifiedPageable)
272+
);
273+
274+
if (!uuidPage.hasContent()) {
275+
return Page.empty();
276+
}
277+
278+
List<UUID> faultResultsUuids = uuidPage
279+
.map(FaultResultRepository.EntityId::getFaultResultUuid)
280+
.toList();
281+
// Then we fetch the main entities data for each UUID
282+
List<FaultResultEntity> faultResults = faultResultRepository.findAllByFaultResultUuidIn(faultResultsUuids);
283+
faultResults.sort(Comparator.comparing(fault -> faultResultsUuids.indexOf(fault.getFaultResultUuid())));
284+
Page<FaultResultEntity> faultResultsPage = new PageImpl<>(faultResults, modifiedPageable, uuidPage.getTotalElements());
285+
286+
if (mode != FaultResultsMode.BASIC) {
287+
// then we append the missing data, and filter some of the Lazy Loaded collections
288+
appendLimitViolationsAndFeederResults(faultResultsPage, childrenSort, resourceFilters);
261289
}
290+
262291
return faultResultsPage;
263292
}
264293

294+
private Pageable filterOutChildrenSort(Pageable pageable, Optional<Sort.Order> childrenSort) {
295+
if (childrenSort.isEmpty()) {
296+
return pageable;
297+
}
298+
return PageRequest.of(
299+
pageable.getPageNumber(),
300+
pageable.getPageSize(),
301+
Sort.by(pageable.getSort().stream().filter(sortOrder ->
302+
!sortOrder.getProperty().equals(childrenSort.get().getProperty())
303+
).toList()
304+
)
305+
);
306+
}
307+
308+
private Optional<Sort.Order> extractChildrenSort(Pageable pageable) {
309+
return pageable.getSort().stream()
310+
.filter(sortOrder ->
311+
sortOrder.getProperty().contains(FeederResultEntity.Fields.connectableId))
312+
.findFirst();
313+
}
314+
265315
@Transactional(readOnly = true)
266316
public Page<FeederResultEntity> findFeederResultsPage(ShortCircuitAnalysisResultEntity result, List<ResourceFilter> resourceFilters, Pageable pageable) {
267317
Objects.requireNonNull(result);
@@ -270,30 +320,78 @@ public Page<FeederResultEntity> findFeederResultsPage(ShortCircuitAnalysisResult
270320
}
271321

272322
@Transactional(readOnly = true)
273-
public Page<FaultResultEntity> findFaultResultsWithLimitViolationsPage(ShortCircuitAnalysisResultEntity result, List<ResourceFilter> resourceFilters, Pageable pageable) {
323+
public Page<FaultResultEntity> findFaultResultsWithLimitViolationsPage(ShortCircuitAnalysisResultEntity result,
324+
List<ResourceFilter> resourceFilters,
325+
Pageable pageable) {
274326
Objects.requireNonNull(result);
327+
328+
Optional<Sort.Order> childrenSort = extractChildrenSort(pageable);
329+
330+
Pageable modifiedPageable = addDefaultSort(filterOutChildrenSort(pageable, childrenSort),
331+
DEFAULT_FAULT_RESULT_SORT_COLUMN);
275332
Specification<FaultResultEntity> specification = FaultResultSpecificationBuilder.buildSpecification(result.getResultUuid(), resourceFilters);
276333
specification = FaultResultSpecificationBuilder.appendWithLimitViolationsToSpecification(specification);
277334
// WARN org.hibernate.hql.internal.ast.QueryTranslatorImpl -
278335
// HHH000104: firstResult/maxResults specified with collection fetch; applying in memory!
279336
// cf. https://vladmihalcea.com/fix-hibernate-hhh000104-entity-fetch-pagination-warning-message/
280337
// We must separate in two requests, one with pagination the other one with Join Fetch
281-
Page<FaultResultEntity> faultResultsPage = faultResultRepository.findAll(specification, addDefaultSort(pageable, DEFAULT_FAULT_RESULT_SORT_COLUMN));
282-
if (faultResultsPage.hasContent()) {
283-
appendLimitViolationsAndFeederResults(faultResultsPage);
338+
339+
Page<FaultResultRepository.EntityId> uuidPage = faultResultRepository.findBy(specification, q ->
340+
q.project(FaultResultEntity.Fields.faultResultUuid)
341+
.as(FaultResultRepository.EntityId.class)
342+
.sortBy(modifiedPageable.getSort())
343+
.page(modifiedPageable)
344+
);
345+
346+
if (!uuidPage.hasContent()) {
347+
return Page.empty();
284348
}
349+
350+
List<UUID> faultResultsUuids = uuidPage
351+
.map(FaultResultRepository.EntityId::getFaultResultUuid)
352+
.toList();
353+
// Then we fetch the main entities data for each UUID
354+
List<FaultResultEntity> faultResults = faultResultRepository.findAllByFaultResultUuidIn(faultResultsUuids);
355+
faultResults.sort(Comparator.comparing(fault -> faultResultsUuids.indexOf(fault.getFaultResultUuid())));
356+
Page<FaultResultEntity> faultResultsPage = new PageImpl<>(faultResults, modifiedPageable, uuidPage.getTotalElements());
357+
358+
// then we append the missing data, and filter some of the Lazy Loaded collections
359+
appendLimitViolationsAndFeederResults(faultResultsPage, childrenSort, resourceFilters);
360+
285361
return faultResultsPage;
286362
}
287363

288-
private void appendLimitViolationsAndFeederResults(Page<FaultResultEntity> pagedFaultResults) {
364+
private void appendLimitViolationsAndFeederResults(Page<FaultResultEntity> pagedFaultResults,
365+
Optional<Sort.Order> childrenSort,
366+
List<ResourceFilter> resourceFilters) {
289367
// using the Hibernate First-Level Cache or Persistence Context
290368
// cf.https://vladmihalcea.com/spring-data-jpa-multiplebagfetchexception/
291369
if (!pagedFaultResults.isEmpty()) {
292370
List<UUID> faultResultsUuids = pagedFaultResults.stream()
293371
.map(FaultResultEntity::getFaultResultUuid)
294-
.collect(Collectors.toList());
372+
.toList();
373+
374+
Specification<FaultResultEntity> specification = FaultResultSpecificationBuilder.buildFeedersSpecification(faultResultsUuids, resourceFilters);
375+
faultResultRepository.findAll(specification);
376+
295377
faultResultRepository.findAllWithLimitViolationsByFaultResultUuidIn(faultResultsUuids);
296-
faultResultRepository.findAllWithFeederResultsByFaultResultUuidIn(faultResultsUuids);
378+
379+
sortFeeders(pagedFaultResults, childrenSort);
380+
}
381+
}
382+
383+
private void sortFeeders(Page<FaultResultEntity> pagedFaultResults, Optional<Sort.Order> childrenSort) {
384+
// feeders may only be sorted by connectableId
385+
if (childrenSort.isPresent()) {
386+
pagedFaultResults.forEach(res -> res.getFeederResults().sort(
387+
childrenSort.get().isAscending() ?
388+
Comparator.comparing(FeederResultEntity::getConnectableId) :
389+
Comparator.comparing(FeederResultEntity::getConnectableId).reversed()
390+
));
391+
} else {
392+
// otherwise by default feederResults (within each individual faultResult) are sorted by 'current' in descending order :
393+
pagedFaultResults.forEach(res -> res.getFeederResults().sort(
394+
Comparator.comparingDouble(FeederResultEntity::getCurrent).reversed()));
297395
}
298396
}
299397

0 commit comments

Comments
 (0)