diff --git a/pom.xml b/pom.xml index 66051ebc..e81fe01f 100644 --- a/pom.xml +++ b/pom.xml @@ -138,10 +138,6 @@ com.powsybl powsybl-ws-commons - - io.projectreactor - reactor-core - org.springframework.cloud spring-cloud-stream @@ -152,11 +148,11 @@ org.springframework.boot - spring-boot-starter-webflux + spring-boot-starter-web org.springdoc - springdoc-openapi-starter-webflux-ui + springdoc-openapi-starter-webmvc-ui diff --git a/src/main/java/org/gridsuite/securityanalysis/server/WebFluxConfig.java b/src/main/java/org/gridsuite/securityanalysis/server/RestTemplateConfig.java similarity index 52% rename from src/main/java/org/gridsuite/securityanalysis/server/WebFluxConfig.java rename to src/main/java/org/gridsuite/securityanalysis/server/RestTemplateConfig.java index a597ec52..ee01e48c 100644 --- a/src/main/java/org/gridsuite/securityanalysis/server/WebFluxConfig.java +++ b/src/main/java/org/gridsuite/securityanalysis/server/RestTemplateConfig.java @@ -1,10 +1,11 @@ +package org.gridsuite.securityanalysis.server; + /** - * Copyright (c) 2020, RTE (http://www.rte-france.com) + * Copyright (c) 2023, RTE (http://www.rte-france.com) * This Source Code Form is subject to the terms of the Mozilla Public * License, v. 2.0. If a copy of the MPL was not distributed with this * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ -package org.gridsuite.securityanalysis.server; import com.fasterxml.jackson.databind.InjectableValues; import com.fasterxml.jackson.databind.ObjectMapper; @@ -15,27 +16,37 @@ import com.powsybl.security.json.SecurityAnalysisJsonModule; import org.springframework.context.annotation.Bean; import org.springframework.context.annotation.Configuration; -import org.springframework.http.codec.ServerCodecConfigurer; -import org.springframework.http.codec.json.Jackson2JsonDecoder; -import org.springframework.http.codec.json.Jackson2JsonEncoder; +import org.springframework.http.converter.HttpMessageConverter; import org.springframework.http.converter.json.Jackson2ObjectMapperBuilder; -import org.springframework.web.reactive.config.WebFluxConfigurer; +import org.springframework.http.converter.json.MappingJackson2HttpMessageConverter; +import org.springframework.web.client.RestTemplate; -/** - * @author Geoffroy Jamgotchian - */ @Configuration -public class WebFluxConfig implements WebFluxConfigurer { +public class RestTemplateConfig { + + @Bean + public RestTemplate restTemplate() { + final RestTemplate restTemplate = new RestTemplate(); + + //find and replace Jackson message converter with our own + for (int i = 0; i < restTemplate.getMessageConverters().size(); i++) { + final HttpMessageConverter httpMessageConverter = restTemplate.getMessageConverters().get(i); + if (httpMessageConverter instanceof MappingJackson2HttpMessageConverter) { + restTemplate.getMessageConverters().set(i, mappingJackson2HttpMessageConverter()); + } + } + + return restTemplate; + } - @Override - public void configureHttpMessageCodecs(ServerCodecConfigurer configurer) { - var objectMapper = objectMapper(); - configurer.defaultCodecs().jackson2JsonEncoder(new Jackson2JsonEncoder(objectMapper)); - configurer.defaultCodecs().jackson2JsonDecoder(new Jackson2JsonDecoder(objectMapper)); + public MappingJackson2HttpMessageConverter mappingJackson2HttpMessageConverter() { + MappingJackson2HttpMessageConverter converter = new MappingJackson2HttpMessageConverter(); + converter.setObjectMapper(objectMapper()); + return converter; } - public static ObjectMapper createObjectMapper() { - var objectMapper = Jackson2ObjectMapperBuilder.json().build(); + private ObjectMapper createObjectMapper() { + ObjectMapper objectMapper = Jackson2ObjectMapperBuilder.json().build(); objectMapper.registerModule(new ContingencyJsonModule()); objectMapper.registerModule(new SecurityAnalysisJsonModule()); objectMapper.registerModule(new LoadFlowParametersJsonModule()); @@ -48,4 +59,6 @@ public static ObjectMapper createObjectMapper() { public ObjectMapper objectMapper() { return createObjectMapper(); } + } + diff --git a/src/main/java/org/gridsuite/securityanalysis/server/SecurityAnalysisApplication.java b/src/main/java/org/gridsuite/securityanalysis/server/SecurityAnalysisApplication.java index 4b4ac4ff..bcebfa00 100644 --- a/src/main/java/org/gridsuite/securityanalysis/server/SecurityAnalysisApplication.java +++ b/src/main/java/org/gridsuite/securityanalysis/server/SecurityAnalysisApplication.java @@ -11,14 +11,12 @@ import org.springframework.boot.SpringApplication; import org.springframework.boot.autoconfigure.SpringBootApplication; import org.springframework.context.annotation.ComponentScan; -import org.springframework.web.reactive.config.EnableWebFlux; /** * @author Geoffroy Jamgotchian */ @SuppressWarnings("checkstyle:HideUtilityClassConstructor") @SpringBootApplication -@EnableWebFlux @ComponentScan(basePackageClasses = {SecurityAnalysisApplication.class, NetworkStoreService.class}) public class SecurityAnalysisApplication { diff --git a/src/main/java/org/gridsuite/securityanalysis/server/SecurityAnalysisController.java b/src/main/java/org/gridsuite/securityanalysis/server/SecurityAnalysisController.java index 561174c3..1133cb28 100644 --- a/src/main/java/org/gridsuite/securityanalysis/server/SecurityAnalysisController.java +++ b/src/main/java/org/gridsuite/securityanalysis/server/SecurityAnalysisController.java @@ -15,8 +15,8 @@ import io.swagger.v3.oas.annotations.responses.ApiResponse; import io.swagger.v3.oas.annotations.responses.ApiResponses; import io.swagger.v3.oas.annotations.tags.Tag; -import org.gridsuite.securityanalysis.server.dto.SubjectLimitViolationToContingencyDTO; -import org.gridsuite.securityanalysis.server.dto.ContingencyToSubjectLimitViolationDTO; +import org.gridsuite.securityanalysis.server.dto.SubjectLimitViolationResultDTO; +import org.gridsuite.securityanalysis.server.dto.ContingencyResultDTO; import org.gridsuite.securityanalysis.server.dto.SecurityAnalysisParametersInfos; import org.gridsuite.securityanalysis.server.dto.SecurityAnalysisStatus; import org.gridsuite.securityanalysis.server.service.SecurityAnalysisRunContext; @@ -25,7 +25,6 @@ import org.springframework.http.MediaType; import org.springframework.http.ResponseEntity; import org.springframework.web.bind.annotation.*; -import reactor.core.publisher.Mono; import java.util.Collections; import java.util.List; @@ -61,7 +60,7 @@ private static List getNonNullOtherNetworkUuids(List otherNetworkUui description = "The security analysis has been performed", content = {@Content(mediaType = APPLICATION_JSON_VALUE, schema = @Schema(implementation = SecurityAnalysisResult.class))})}) - public ResponseEntity> run(@Parameter(description = "Network UUID") @PathVariable("networkUuid") UUID networkUuid, + public ResponseEntity run(@Parameter(description = "Network UUID") @PathVariable("networkUuid") UUID networkUuid, @Parameter(description = "Variant Id") @RequestParam(name = "variantId", required = false) String variantId, @Parameter(description = "Other networks UUID (to merge with main one))") @RequestParam(name = "networkUuid", required = false) List otherNetworkUuids, @Parameter(description = "Contingency list name") @RequestParam(name = "contingencyListName", required = false) List contigencyListNames, @@ -71,7 +70,7 @@ public ResponseEntity> run(@Parameter(description = @RequestBody(required = false) SecurityAnalysisParametersInfos parameters) { String providerToUse = provider != null ? provider : service.getDefaultProvider(); List nonNullOtherNetworkUuids = getNonNullOtherNetworkUuids(otherNetworkUuids); - Mono result = workerService.run(new SecurityAnalysisRunContext(networkUuid, variantId, nonNullOtherNetworkUuids, contigencyListNames, null, providerToUse, parameters, reportUuid, reporterId)); + SecurityAnalysisResult result = workerService.run(new SecurityAnalysisRunContext(networkUuid, variantId, nonNullOtherNetworkUuids, contigencyListNames, null, providerToUse, parameters, reportUuid, reporterId)); return ResponseEntity.ok().contentType(MediaType.APPLICATION_JSON).body(result); } @@ -81,7 +80,7 @@ public ResponseEntity> run(@Parameter(description = description = "The security analysis has been performed and results have been saved to database", content = {@Content(mediaType = APPLICATION_JSON_VALUE, schema = @Schema(implementation = SecurityAnalysisResult.class))})}) - public ResponseEntity> runAndSave(@Parameter(description = "Network UUID") @PathVariable("networkUuid") UUID networkUuid, + public ResponseEntity runAndSave(@Parameter(description = "Network UUID") @PathVariable("networkUuid") UUID networkUuid, @Parameter(description = "Variant Id") @RequestParam(name = "variantId", required = false) String variantId, @Parameter(description = "Other networks UUID (to merge with main one))") @RequestParam(name = "networkUuid", required = false) List otherNetworkUuids, @Parameter(description = "Contingency list name") @RequestParam(name = "contingencyListName", required = false) List contigencyListNames, @@ -92,7 +91,7 @@ public ResponseEntity> runAndSave(@Parameter(description = "Network U @RequestBody(required = false) SecurityAnalysisParametersInfos parameters) { String providerToUse = provider != null ? provider : service.getDefaultProvider(); List nonNullOtherNetworkUuids = getNonNullOtherNetworkUuids(otherNetworkUuids); - Mono resultUuid = service.runAndSaveResult(new SecurityAnalysisRunContext(networkUuid, variantId, nonNullOtherNetworkUuids, contigencyListNames, receiver, providerToUse, parameters, reportUuid, reporterId)); + UUID resultUuid = service.runAndSaveResult(new SecurityAnalysisRunContext(networkUuid, variantId, nonNullOtherNetworkUuids, contigencyListNames, receiver, providerToUse, parameters, reportUuid, reporterId)); return ResponseEntity.ok().contentType(MediaType.APPLICATION_JSON).body(resultUuid); } @@ -100,73 +99,77 @@ public ResponseEntity> runAndSave(@Parameter(description = "Network U @Operation(summary = "Get a security analysis result from the database - N result") @ApiResponses(value = {@ApiResponse(responseCode = "200", description = "The security analysis result"), @ApiResponse(responseCode = "404", description = "Security analysis result has not been found")}) - public Mono> getNResult(@Parameter(description = "Result UUID") @PathVariable("resultUuid") UUID resultUuid) { + public ResponseEntity getNResult(@Parameter(description = "Result UUID") @PathVariable("resultUuid") UUID resultUuid) { + PreContingencyResult result = service.getNResult(resultUuid); - Mono result = service.getNResult(resultUuid); - return result.map(r -> ResponseEntity.ok().contentType(MediaType.APPLICATION_JSON).body(r)) - .defaultIfEmpty(ResponseEntity.notFound().build()); + return result != null + ? ResponseEntity.ok().contentType(MediaType.APPLICATION_JSON).body(result) + : ResponseEntity.notFound().build(); } @GetMapping(value = "/results/{resultUuid}/nmk-contingencies-result", produces = APPLICATION_JSON_VALUE) @Operation(summary = "Get a security analysis result from the database - NMK contingencies result") @ApiResponses(value = {@ApiResponse(responseCode = "200", description = "The security analysis result"), @ApiResponse(responseCode = "404", description = "Security analysis result has not been found")}) - public Mono>> getNmKContingenciesResult(@Parameter(description = "Result UUID") @PathVariable("resultUuid") UUID resultUuid) { + public ResponseEntity> getNmKContingenciesResult(@Parameter(description = "Result UUID") @PathVariable("resultUuid") UUID resultUuid) { + List result = service.getNmKContingenciesResult(resultUuid); - Mono> result = service.getNmKContingenciesResult(resultUuid); - return result.map(r -> ResponseEntity.ok().contentType(MediaType.APPLICATION_JSON).body(r)) - .defaultIfEmpty(ResponseEntity.notFound().build()); + return result != null + ? ResponseEntity.ok().contentType(MediaType.APPLICATION_JSON).body(result) + : ResponseEntity.notFound().build(); } @GetMapping(value = "/results/{resultUuid}/nmk-constraints-result", produces = APPLICATION_JSON_VALUE) @Operation(summary = "Get a security analysis result from the database - NMK contingencies result") @ApiResponses(value = {@ApiResponse(responseCode = "200", description = "The security analysis result"), @ApiResponse(responseCode = "404", description = "Security analysis result has not been found")}) - public Mono>> getNmKConstraintsResult(@Parameter(description = "Result UUID") @PathVariable("resultUuid") UUID resultUuid) { + public ResponseEntity> getNmKConstraintsResult(@Parameter(description = "Result UUID") @PathVariable("resultUuid") UUID resultUuid) { - Mono> result = service.getNmKConstraintsResult(resultUuid); - return result.map(r -> ResponseEntity.ok().contentType(MediaType.APPLICATION_JSON).body(r)) - .defaultIfEmpty(ResponseEntity.notFound().build()); + List result = service.getNmKConstraintsResult(resultUuid); + return result != null + ? ResponseEntity.ok().contentType(MediaType.APPLICATION_JSON).body(result) + : ResponseEntity.notFound().build(); } @DeleteMapping(value = "/results/{resultUuid}", produces = APPLICATION_JSON_VALUE) @Operation(summary = "Delete a security analysis result from the database") @ApiResponses(value = {@ApiResponse(responseCode = "200", description = "The security analysis result has been deleted")}) - public ResponseEntity> deleteResult(@Parameter(description = "Result UUID") @PathVariable("resultUuid") UUID resultUuid) { - Mono result = service.deleteResult(resultUuid); - return ResponseEntity.ok().body(result); + public ResponseEntity deleteResult(@Parameter(description = "Result UUID") @PathVariable("resultUuid") UUID resultUuid) { + service.deleteResult(resultUuid); + return ResponseEntity.ok().build(); } @DeleteMapping(value = "/results", produces = APPLICATION_JSON_VALUE) @Operation(summary = "Delete all security analysis results from the database") @ApiResponses(value = {@ApiResponse(responseCode = "200", description = "All security analysis results have been deleted")}) - public ResponseEntity> deleteResults() { - Mono result = service.deleteResults(); - return ResponseEntity.ok().body(result); + public ResponseEntity deleteResults() { + service.deleteResults(); + return ResponseEntity.ok().build(); } @GetMapping(value = "/results/{resultUuid}/status", produces = APPLICATION_JSON_VALUE) @Operation(summary = "Get the security analysis status from the database") @ApiResponses(value = {@ApiResponse(responseCode = "200", description = "The security analysis status")}) - public ResponseEntity> getStatus(@Parameter(description = "Result UUID") @PathVariable("resultUuid") UUID resultUuid) { - Mono result = service.getStatus(resultUuid); + public ResponseEntity getStatus(@Parameter(description = "Result UUID") @PathVariable("resultUuid") UUID resultUuid) { + SecurityAnalysisStatus result = service.getStatus(resultUuid); return ResponseEntity.ok().body(result); } @PutMapping(value = "/results/invalidate-status", produces = APPLICATION_JSON_VALUE) @Operation(summary = "Invalidate the security analysis status from the database") @ApiResponses(value = {@ApiResponse(responseCode = "200", description = "The security analysis status has been invalidated")}) - public ResponseEntity> invalidateStatus(@Parameter(description = "Result uuids") @RequestParam(name = "resultUuid") List resultUuids) { - return ResponseEntity.ok().body(service.setStatus(resultUuids, SecurityAnalysisStatus.NOT_DONE)); + public ResponseEntity invalidateStatus(@Parameter(description = "Result uuids") @RequestParam(name = "resultUuid") List resultUuids) { + service.setStatus(resultUuids, SecurityAnalysisStatus.NOT_DONE); + return ResponseEntity.ok().build(); } @PutMapping(value = "/results/{resultUuid}/stop", produces = APPLICATION_JSON_VALUE) @Operation(summary = "Stop a security analysis computation") @ApiResponses(value = {@ApiResponse(responseCode = "200", description = "The security analysis has been stopped")}) - public ResponseEntity> stop(@Parameter(description = "Result UUID") @PathVariable("resultUuid") UUID resultUuid, + public ResponseEntity stop(@Parameter(description = "Result UUID") @PathVariable("resultUuid") UUID resultUuid, @Parameter(description = "Result receiver") @RequestParam(name = "receiver", required = false) String receiver) { - Mono result = service.stop(resultUuid, receiver); - return ResponseEntity.ok().body(result); + service.stop(resultUuid, receiver); + return ResponseEntity.ok().build(); } @GetMapping(value = "/providers", produces = APPLICATION_JSON_VALUE) diff --git a/src/main/java/org/gridsuite/securityanalysis/server/dto/ContingencyDTO.java b/src/main/java/org/gridsuite/securityanalysis/server/dto/ContingencyDTO.java new file mode 100644 index 00000000..69b7eec4 --- /dev/null +++ b/src/main/java/org/gridsuite/securityanalysis/server/dto/ContingencyDTO.java @@ -0,0 +1,28 @@ +package org.gridsuite.securityanalysis.server.dto; + +import lombok.AllArgsConstructor; +import lombok.Builder; +import lombok.Getter; +import lombok.NoArgsConstructor; +import org.gridsuite.securityanalysis.server.entities.ContingencyEntity; + +import java.util.List; +import java.util.stream.Collectors; + +@AllArgsConstructor +@NoArgsConstructor +@Getter +@Builder +public class ContingencyDTO { + private String contingencyId; + private String computationStatus; + private List elements; + + public static ContingencyDTO toDto(ContingencyEntity contingency) { + return ContingencyDTO.builder() + .contingencyId(contingency.getContingencyId()) + .computationStatus(contingency.getStatus()) + .elements(contingency.getContingencyElements().stream().map(ContingencyElementDTO::toDto).collect(Collectors.toList())) + .build(); + } +} diff --git a/src/main/java/org/gridsuite/securityanalysis/server/dto/ContingencyElementDTO.java b/src/main/java/org/gridsuite/securityanalysis/server/dto/ContingencyElementDTO.java index c69325e5..4e6b9a2f 100644 --- a/src/main/java/org/gridsuite/securityanalysis/server/dto/ContingencyElementDTO.java +++ b/src/main/java/org/gridsuite/securityanalysis/server/dto/ContingencyElementDTO.java @@ -8,6 +8,7 @@ import com.powsybl.contingency.ContingencyElementType; import lombok.AllArgsConstructor; +import lombok.Builder; import lombok.Getter; import lombok.NoArgsConstructor; import org.gridsuite.securityanalysis.server.entities.ContingencyElementEmbeddable; @@ -16,6 +17,7 @@ */ @Getter +@Builder @AllArgsConstructor @NoArgsConstructor public class ContingencyElementDTO { @@ -23,6 +25,9 @@ public class ContingencyElementDTO { private ContingencyElementType elementType; public static ContingencyElementDTO toDto(ContingencyElementEmbeddable contingencyElement) { - return new ContingencyElementDTO(contingencyElement.getElementId(), contingencyElement.getElementType()); + return ContingencyElementDTO.builder() + .id(contingencyElement.getElementId()) + .elementType(contingencyElement.getElementType()) + .build(); } } diff --git a/src/main/java/org/gridsuite/securityanalysis/server/dto/ContingencyFromSubjectLimitViolationDTO.java b/src/main/java/org/gridsuite/securityanalysis/server/dto/ContingencyFromSubjectLimitViolationDTO.java deleted file mode 100644 index 592ebbb9..00000000 --- a/src/main/java/org/gridsuite/securityanalysis/server/dto/ContingencyFromSubjectLimitViolationDTO.java +++ /dev/null @@ -1,74 +0,0 @@ -/** - * Copyright (c) 2023, RTE (http://www.rte-france.com) - * This Source Code Form is subject to the terms of the Mozilla Public - * License, v. 2.0. If a copy of the MPL was not distributed with this - * file, You can obtain one at http://mozilla.org/MPL/2.0/. - */ -package org.gridsuite.securityanalysis.server.dto; - -import com.powsybl.iidm.network.Branch; -import com.powsybl.security.LimitViolationType; -import lombok.AllArgsConstructor; -import lombok.Getter; -import lombok.NoArgsConstructor; -import org.gridsuite.securityanalysis.server.entities.ContingencyEntity; -import org.gridsuite.securityanalysis.server.entities.ContingencyLimitViolationEntity; - -import java.util.List; -import java.util.stream.Collectors; -/** - * @author Kevin Le Saulnier - */ - -@Getter -@AllArgsConstructor -@NoArgsConstructor -public class ContingencyFromSubjectLimitViolationDTO { - private String contingencyId; - private String computationStatus; - private LimitViolationType limitType; - private String limitName; - private Branch.Side side; - private int acceptableDuration; - private double limit; - private double limitReduction; - private double value; - private List elements; - private Double loading; - - public ContingencyFromSubjectLimitViolationDTO(String contingencyId, String computationStatus, LimitViolationType limitType, String limitName, Branch.Side side, int acceptableDuration, double limit, double limitReduction, double value, List elements) { - this.contingencyId = contingencyId; - this.computationStatus = computationStatus; - this.limitType = limitType; - this.limitName = limitName; - this.side = side; - this.acceptableDuration = acceptableDuration; - this.limit = limit; - this.limitReduction = limitReduction; - this.value = value; - this.elements = elements; - - Double computedLoading = LimitViolationType.CURRENT.equals(limitType) - ? (100 * value) / (limit * limitReduction) - : null; - - this.loading = computedLoading; - } - - public static ContingencyFromSubjectLimitViolationDTO toDto(ContingencyLimitViolationEntity limitViolation) { - ContingencyEntity contingency = limitViolation.getContingency(); - - return new ContingencyFromSubjectLimitViolationDTO( - contingency.getContingencyId(), - contingency.getStatus(), - limitViolation.getLimitType(), - limitViolation.getLimitName(), - limitViolation.getSide(), - limitViolation.getAcceptableDuration(), - limitViolation.getLimit(), - limitViolation.getLimitReduction(), - limitViolation.getValue(), - contingency.getContingencyElements().stream().map(ContingencyElementDTO::toDto).collect(Collectors.toList()) - ); - } -} diff --git a/src/main/java/org/gridsuite/securityanalysis/server/dto/ContingencyLimitViolationDTO.java b/src/main/java/org/gridsuite/securityanalysis/server/dto/ContingencyLimitViolationDTO.java new file mode 100644 index 00000000..798fd02c --- /dev/null +++ b/src/main/java/org/gridsuite/securityanalysis/server/dto/ContingencyLimitViolationDTO.java @@ -0,0 +1,33 @@ +/** + * Copyright (c) 2023, RTE (http://www.rte-france.com) + * This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. + */ +package org.gridsuite.securityanalysis.server.dto; + +import lombok.AllArgsConstructor; +import lombok.Builder; +import lombok.Getter; +import lombok.NoArgsConstructor; +import org.gridsuite.securityanalysis.server.entities.ContingencyLimitViolationEntity; +/** + * @author Kevin Le Saulnier + */ + +@Getter +@AllArgsConstructor +@NoArgsConstructor +@Builder +public class ContingencyLimitViolationDTO { + private ContingencyDTO contingency; + + private LimitViolationDTO limitViolation; + + public static ContingencyLimitViolationDTO toDto(ContingencyLimitViolationEntity limitViolation) { + return ContingencyLimitViolationDTO.builder() + .contingency(ContingencyDTO.toDto(limitViolation.getContingency())) + .limitViolation(LimitViolationDTO.toDto(limitViolation)) + .build(); + } +} diff --git a/src/main/java/org/gridsuite/securityanalysis/server/dto/ContingencyResultDTO.java b/src/main/java/org/gridsuite/securityanalysis/server/dto/ContingencyResultDTO.java new file mode 100644 index 00000000..71a61fe6 --- /dev/null +++ b/src/main/java/org/gridsuite/securityanalysis/server/dto/ContingencyResultDTO.java @@ -0,0 +1,39 @@ +/** + * Copyright (c) 2023, RTE (http://www.rte-france.com) + * This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. + */ +package org.gridsuite.securityanalysis.server.dto; + +import lombok.AllArgsConstructor; +import lombok.Builder; +import lombok.Getter; +import lombok.NoArgsConstructor; +import org.gridsuite.securityanalysis.server.entities.ContingencyEntity; + +import java.util.List; +/** + * @author Kevin Le Saulnier + */ + +@Getter +@AllArgsConstructor +@NoArgsConstructor +@Builder +public class ContingencyResultDTO { + private ContingencyDTO contingency; + + private List subjectLimitViolations; + + public static ContingencyResultDTO toDto(ContingencyEntity contingency) { + List subjectLimitViolations = contingency.getContingencyLimitViolations().stream() + .map(SubjectLimitViolationDTO::toDto) + .toList(); + + return ContingencyResultDTO.builder() + .contingency(ContingencyDTO.toDto(contingency)) + .subjectLimitViolations(subjectLimitViolations) + .build(); + } +} diff --git a/src/main/java/org/gridsuite/securityanalysis/server/dto/ContingencyToSubjectLimitViolationDTO.java b/src/main/java/org/gridsuite/securityanalysis/server/dto/ContingencyToSubjectLimitViolationDTO.java deleted file mode 100644 index 8beb2c98..00000000 --- a/src/main/java/org/gridsuite/securityanalysis/server/dto/ContingencyToSubjectLimitViolationDTO.java +++ /dev/null @@ -1,26 +0,0 @@ -/** - * Copyright (c) 2023, RTE (http://www.rte-france.com) - * This Source Code Form is subject to the terms of the Mozilla Public - * License, v. 2.0. If a copy of the MPL was not distributed with this - * file, You can obtain one at http://mozilla.org/MPL/2.0/. - */ -package org.gridsuite.securityanalysis.server.dto; - -import lombok.AllArgsConstructor; -import lombok.Getter; -import lombok.NoArgsConstructor; - -import java.util.List; -/** - * @author Kevin Le Saulnier - */ - -@Getter -@AllArgsConstructor -@NoArgsConstructor -public class ContingencyToSubjectLimitViolationDTO { - private String id; - private String status; - private List elements; - private List subjectLimitViolations; -} diff --git a/src/main/java/org/gridsuite/securityanalysis/server/dto/LimitViolationDTO.java b/src/main/java/org/gridsuite/securityanalysis/server/dto/LimitViolationDTO.java new file mode 100644 index 00000000..b934565e --- /dev/null +++ b/src/main/java/org/gridsuite/securityanalysis/server/dto/LimitViolationDTO.java @@ -0,0 +1,41 @@ +package org.gridsuite.securityanalysis.server.dto; + +import com.powsybl.iidm.network.Branch; +import com.powsybl.security.LimitViolationType; +import lombok.AllArgsConstructor; +import lombok.Builder; +import lombok.Getter; +import lombok.NoArgsConstructor; +import org.gridsuite.securityanalysis.server.entities.AbstractLimitViolationEntity; + +@AllArgsConstructor +@NoArgsConstructor +@Builder +@Getter +public class LimitViolationDTO { + private LimitViolationType limitType; + private String limitName; + private Branch.Side side; + private int acceptableDuration; + private double limit; + private double limitReduction; + private double value; + private Double loading; + + public static LimitViolationDTO toDto(AbstractLimitViolationEntity limitViolation) { + Double computedLoading = LimitViolationType.CURRENT.equals(limitViolation.getLimitType()) + ? (100 * limitViolation.getValue()) / (limitViolation.getLimit() * limitViolation.getLimitReduction()) + : null; + + return LimitViolationDTO.builder() + .limitType(limitViolation.getLimitType()) + .limitName(limitViolation.getLimitName()) + .side(limitViolation.getSide()) + .acceptableDuration(limitViolation.getAcceptableDuration()) + .limit(limitViolation.getLimit()) + .limitReduction(limitViolation.getLimitReduction()) + .value(limitViolation.getValue()) + .loading(computedLoading) + .build(); + } +} diff --git a/src/main/java/org/gridsuite/securityanalysis/server/dto/SubjectLimitViolationDTO.java b/src/main/java/org/gridsuite/securityanalysis/server/dto/SubjectLimitViolationDTO.java new file mode 100644 index 00000000..67e6cad0 --- /dev/null +++ b/src/main/java/org/gridsuite/securityanalysis/server/dto/SubjectLimitViolationDTO.java @@ -0,0 +1,37 @@ +/** + * Copyright (c) 2023, RTE (http://www.rte-france.com) + * This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. + */ +package org.gridsuite.securityanalysis.server.dto; + +import lombok.AllArgsConstructor; +import lombok.Builder; +import lombok.Getter; +import lombok.NoArgsConstructor; +import org.gridsuite.securityanalysis.server.entities.ContingencyLimitViolationEntity; +/** + * @author Kevin Le Saulnier + */ + +@Getter +@AllArgsConstructor +@NoArgsConstructor +@Builder +public class SubjectLimitViolationDTO { + private String subjectId; + + private LimitViolationDTO limitViolation; + + public static SubjectLimitViolationDTO toDto(ContingencyLimitViolationEntity limitViolation) { + String subjectId = limitViolation.getSubjectLimitViolation() != null + ? limitViolation.getSubjectLimitViolation().getSubjectId() + : null; + + return SubjectLimitViolationDTO.builder() + .subjectId(subjectId) + .limitViolation(LimitViolationDTO.toDto(limitViolation)) + .build(); + } +} diff --git a/src/main/java/org/gridsuite/securityanalysis/server/dto/SubjectLimitViolationFromContingencyDTO.java b/src/main/java/org/gridsuite/securityanalysis/server/dto/SubjectLimitViolationFromContingencyDTO.java deleted file mode 100644 index be614553..00000000 --- a/src/main/java/org/gridsuite/securityanalysis/server/dto/SubjectLimitViolationFromContingencyDTO.java +++ /dev/null @@ -1,57 +0,0 @@ -/** - * Copyright (c) 2023, RTE (http://www.rte-france.com) - * This Source Code Form is subject to the terms of the Mozilla Public - * License, v. 2.0. If a copy of the MPL was not distributed with this - * file, You can obtain one at http://mozilla.org/MPL/2.0/. - */ -package org.gridsuite.securityanalysis.server.dto; - -import com.powsybl.iidm.network.Branch; -import com.powsybl.security.LimitViolationType; -import lombok.AllArgsConstructor; -import lombok.Getter; -import lombok.NoArgsConstructor; -import org.gridsuite.securityanalysis.server.entities.ContingencyLimitViolationEntity; -/** - * @author Kevin Le Saulnier - */ - -@Getter -@AllArgsConstructor -@NoArgsConstructor -public class SubjectLimitViolationFromContingencyDTO { - private String subjectId; - private LimitViolationType limitType; - private String limitName; - private Branch.Side side; - private int acceptableDuration; - private double limit; - private double limitReduction; - private double value; - private Double loading; - - public SubjectLimitViolationFromContingencyDTO(String subjectId, LimitViolationType limitType, String limitName, Branch.Side side, int acceptableDuration, double limit, double limitReduction, double value) { - this.subjectId = subjectId; - this.limitType = limitType; - this.limitName = limitName; - this.side = side; - this.acceptableDuration = acceptableDuration; - this.limit = limit; - this.limitReduction = limitReduction; - this.value = value; - - Double computedLoading = LimitViolationType.CURRENT.equals(limitType) - ? (100 * value) / (limit * limitReduction) - : null; - - this.loading = computedLoading; - } - - public static SubjectLimitViolationFromContingencyDTO toDto(ContingencyLimitViolationEntity limitViolation) { - String subjectId = limitViolation.getSubjectLimitViolation() != null - ? limitViolation.getSubjectLimitViolation().getSubjectId() - : null; - - return new SubjectLimitViolationFromContingencyDTO(subjectId, limitViolation.getLimitType(), limitViolation.getLimitName(), limitViolation.getSide(), limitViolation.getAcceptableDuration(), limitViolation.getLimit(), limitViolation.getLimitReduction(), limitViolation.getValue()); - } -} diff --git a/src/main/java/org/gridsuite/securityanalysis/server/dto/SubjectLimitViolationResultDTO.java b/src/main/java/org/gridsuite/securityanalysis/server/dto/SubjectLimitViolationResultDTO.java new file mode 100644 index 00000000..6b33757a --- /dev/null +++ b/src/main/java/org/gridsuite/securityanalysis/server/dto/SubjectLimitViolationResultDTO.java @@ -0,0 +1,39 @@ +/** + * Copyright (c) 2023, RTE (http://www.rte-france.com) + * This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. + */ +package org.gridsuite.securityanalysis.server.dto; + +import lombok.AllArgsConstructor; +import lombok.Builder; +import lombok.Getter; +import lombok.NoArgsConstructor; +import org.gridsuite.securityanalysis.server.entities.SubjectLimitViolationEntity; + +import java.util.List; +/** + * @author Kevin Le Saulnier + */ + +@Getter +@Builder +@AllArgsConstructor +@NoArgsConstructor +public class SubjectLimitViolationResultDTO { + private String subjectId; + + private List contingencies; + + public static SubjectLimitViolationResultDTO toDto(SubjectLimitViolationEntity subjectLimitViolation) { + List contingencies = subjectLimitViolation.getContingencyLimitViolations().stream() + .map(ContingencyLimitViolationDTO::toDto) + .toList(); + + return SubjectLimitViolationResultDTO.builder() + .subjectId(subjectLimitViolation.getSubjectId()) + .contingencies(contingencies) + .build(); + } +} diff --git a/src/main/java/org/gridsuite/securityanalysis/server/dto/SubjectLimitViolationToContingencyDTO.java b/src/main/java/org/gridsuite/securityanalysis/server/dto/SubjectLimitViolationToContingencyDTO.java deleted file mode 100644 index 50484d51..00000000 --- a/src/main/java/org/gridsuite/securityanalysis/server/dto/SubjectLimitViolationToContingencyDTO.java +++ /dev/null @@ -1,25 +0,0 @@ -/** - * Copyright (c) 2023, RTE (http://www.rte-france.com) - * This Source Code Form is subject to the terms of the Mozilla Public - * License, v. 2.0. If a copy of the MPL was not distributed with this - * file, You can obtain one at http://mozilla.org/MPL/2.0/. - */ -package org.gridsuite.securityanalysis.server.dto; - -import lombok.AllArgsConstructor; -import lombok.Getter; -import lombok.NoArgsConstructor; - -import java.util.List; -/** - * @author Kevin Le Saulnier - */ - -@Getter -@AllArgsConstructor -@NoArgsConstructor -public class SubjectLimitViolationToContingencyDTO { - private String subjectId; - - private List contingencies; -} diff --git a/src/main/java/org/gridsuite/securityanalysis/server/entities/AbstractLimitViolationEntity.java b/src/main/java/org/gridsuite/securityanalysis/server/entities/AbstractLimitViolationEntity.java index afa32568..e8fd5411 100644 --- a/src/main/java/org/gridsuite/securityanalysis/server/entities/AbstractLimitViolationEntity.java +++ b/src/main/java/org/gridsuite/securityanalysis/server/entities/AbstractLimitViolationEntity.java @@ -10,8 +10,8 @@ import com.powsybl.security.LimitViolation; import com.powsybl.security.LimitViolationType; import jakarta.persistence.*; -import lombok.Getter; -import lombok.NoArgsConstructor; +import lombok.*; +import lombok.experimental.SuperBuilder; import java.util.UUID; /** @@ -19,6 +19,8 @@ */ @NoArgsConstructor +@AllArgsConstructor +@SuperBuilder @Getter @MappedSuperclass public abstract class AbstractLimitViolationEntity { @@ -30,8 +32,6 @@ public abstract class AbstractLimitViolationEntity { @ManyToOne private SubjectLimitViolationEntity subjectLimitViolation; - private String subjectName; - @Column(name = "limitValue") private double limit; @@ -50,18 +50,6 @@ public abstract class AbstractLimitViolationEntity { @Enumerated(EnumType.STRING) private Branch.Side side; - protected AbstractLimitViolationEntity(SubjectLimitViolationEntity subjectLimitViolation, String subjectName, double limit, String limitName, LimitViolationType limitType, int acceptableDuration, float limitReduction, double value, Branch.Side side) { - this.subjectLimitViolation = subjectLimitViolation; - this.subjectName = subjectName; - this.limit = limit; - this.limitName = limitName; - this.limitType = limitType; - this.acceptableDuration = acceptableDuration; - this.limitReduction = limitReduction; - this.value = value; - this.side = side; - } - public static LimitViolation toLimitViolation(AbstractLimitViolationEntity limitViolationEntity) { String subjectId = limitViolationEntity.getSubjectLimitViolation() != null ? limitViolationEntity.getSubjectLimitViolation().getSubjectId() diff --git a/src/main/java/org/gridsuite/securityanalysis/server/entities/ContingencyElementEmbeddable.java b/src/main/java/org/gridsuite/securityanalysis/server/entities/ContingencyElementEmbeddable.java index 6bbac0fd..5061bd79 100644 --- a/src/main/java/org/gridsuite/securityanalysis/server/entities/ContingencyElementEmbeddable.java +++ b/src/main/java/org/gridsuite/securityanalysis/server/entities/ContingencyElementEmbeddable.java @@ -11,6 +11,7 @@ import jakarta.persistence.Column; import jakarta.persistence.Embeddable; import lombok.AllArgsConstructor; +import lombok.Builder; import lombok.Getter; import lombok.NoArgsConstructor; @@ -18,6 +19,7 @@ * @author Laurent GARNIER */ @Getter +@Builder @NoArgsConstructor @AllArgsConstructor @Embeddable @@ -30,6 +32,9 @@ public class ContingencyElementEmbeddable { private String elementId; public static ContingencyElementEmbeddable toEntity(ContingencyElement contingencyElement) { - return new ContingencyElementEmbeddable(contingencyElement.getType(), contingencyElement.getId()); + return ContingencyElementEmbeddable.builder() + .elementType(contingencyElement.getType()) + .elementId(contingencyElement.getId()) + .build(); } } diff --git a/src/main/java/org/gridsuite/securityanalysis/server/entities/ContingencyLimitViolationEntity.java b/src/main/java/org/gridsuite/securityanalysis/server/entities/ContingencyLimitViolationEntity.java index 25e82aed..daa56062 100644 --- a/src/main/java/org/gridsuite/securityanalysis/server/entities/ContingencyLimitViolationEntity.java +++ b/src/main/java/org/gridsuite/securityanalysis/server/entities/ContingencyLimitViolationEntity.java @@ -6,37 +6,40 @@ */ package org.gridsuite.securityanalysis.server.entities; -import com.powsybl.iidm.network.Branch; import com.powsybl.security.LimitViolation; -import com.powsybl.security.LimitViolationType; import jakarta.persistence.*; -import lombok.Getter; -import lombok.NoArgsConstructor; -import lombok.Setter; +import lombok.*; +import lombok.experimental.SuperBuilder; + /** * @author Kevin Le Saulnier */ +@Data @NoArgsConstructor -@Entity +@SuperBuilder @Getter +@Entity @Table(name = "contingency_limit_violation") public class ContingencyLimitViolationEntity extends AbstractLimitViolationEntity { @ManyToOne(fetch = FetchType.LAZY) @Setter private ContingencyEntity contingency; - public ContingencyLimitViolationEntity(SubjectLimitViolationEntity subjectLimitViolation, String subjectName, double limit, String limitName, LimitViolationType limitType, int acceptableDuration, float limitReduction, double value, Branch.Side side) { - super(subjectLimitViolation, subjectName, limit, limitName, limitType, acceptableDuration, limitReduction, value, side); - if (subjectLimitViolation != null) { - subjectLimitViolation.addContingencyLimitViolation(this); - } - } - public static ContingencyLimitViolationEntity toEntity(LimitViolation limitViolation, SubjectLimitViolationEntity subjectLimitViolation) { - return new ContingencyLimitViolationEntity(subjectLimitViolation, - limitViolation.getSubjectName(), limitViolation.getLimit(), limitViolation.getLimitName(), - limitViolation.getLimitType(), limitViolation.getAcceptableDuration(), limitViolation.getLimitReduction(), limitViolation.getValue(), - limitViolation.getSide()); + ContingencyLimitViolationEntity contingencyLimitViolationEntity = ContingencyLimitViolationEntity.builder() + .limit(limitViolation.getLimit()) + .limitName(limitViolation.getLimitName()) + .limitType(limitViolation.getLimitType()) + .acceptableDuration(limitViolation.getAcceptableDuration()) + .limitReduction(limitViolation.getLimitReduction()) + .value(limitViolation.getValue()) + .side(limitViolation.getSide()) + .subjectLimitViolation(subjectLimitViolation) + .build(); + + subjectLimitViolation.addContingencyLimitViolation(contingencyLimitViolationEntity); + + return contingencyLimitViolationEntity; } } diff --git a/src/main/java/org/gridsuite/securityanalysis/server/entities/PreContingencyLimitViolationEntity.java b/src/main/java/org/gridsuite/securityanalysis/server/entities/PreContingencyLimitViolationEntity.java index fecc4f04..35b2b881 100644 --- a/src/main/java/org/gridsuite/securityanalysis/server/entities/PreContingencyLimitViolationEntity.java +++ b/src/main/java/org/gridsuite/securityanalysis/server/entities/PreContingencyLimitViolationEntity.java @@ -6,16 +6,16 @@ */ package org.gridsuite.securityanalysis.server.entities; -import com.powsybl.iidm.network.Branch; import com.powsybl.security.LimitViolation; -import com.powsybl.security.LimitViolationType; import com.powsybl.security.results.PreContingencyResult; import jakarta.persistence.Entity; import jakarta.persistence.ManyToOne; import jakarta.persistence.Table; +import lombok.Data; import lombok.Getter; import lombok.NoArgsConstructor; import lombok.Setter; +import lombok.experimental.SuperBuilder; import java.util.List; import java.util.Map; @@ -25,7 +25,9 @@ * @author Kevin Le Saulnier */ +@Data @NoArgsConstructor +@SuperBuilder @Getter @Entity @Table(name = "pre_contingency_limit_violation") @@ -35,18 +37,20 @@ public class PreContingencyLimitViolationEntity extends AbstractLimitViolationEn @Setter SecurityAnalysisResultEntity result; - public PreContingencyLimitViolationEntity(SubjectLimitViolationEntity subjectLimitViolation, String subjectName, double limit, String limitName, LimitViolationType limitType, int acceptableDuration, float limitReduction, double value, Branch.Side side) { - super(subjectLimitViolation, subjectName, limit, limitName, limitType, acceptableDuration, limitReduction, value, side); - } - public static List toEntityList(PreContingencyResult preContingencyResult, Map subjectLimitViolationsBySubjectId) { - return preContingencyResult.getLimitViolationsResult().getLimitViolations().stream().map(limitViolation -> toEntityList(limitViolation, subjectLimitViolationsBySubjectId.get(limitViolation.getSubjectId()))).collect(Collectors.toList()); + return preContingencyResult.getLimitViolationsResult().getLimitViolations().stream().map(limitViolation -> toEntity(limitViolation, subjectLimitViolationsBySubjectId.get(limitViolation.getSubjectId()))).collect(Collectors.toList()); } - public static PreContingencyLimitViolationEntity toEntityList(LimitViolation limitViolation, SubjectLimitViolationEntity subjectLimitViolation) { - return new PreContingencyLimitViolationEntity(subjectLimitViolation, - limitViolation.getSubjectName(), limitViolation.getLimit(), limitViolation.getLimitName(), - limitViolation.getLimitType(), limitViolation.getAcceptableDuration(), limitViolation.getLimitReduction(), limitViolation.getValue(), - limitViolation.getSide()); + public static PreContingencyLimitViolationEntity toEntity(LimitViolation limitViolation, SubjectLimitViolationEntity subjectLimitViolation) { + return PreContingencyLimitViolationEntity.builder() + .subjectLimitViolation(subjectLimitViolation) + .limit(limitViolation.getLimit()) + .limitName(limitViolation.getLimitName()) + .limitType(limitViolation.getLimitType()) + .acceptableDuration(limitViolation.getAcceptableDuration()) + .limitReduction(limitViolation.getLimitReduction()) + .value(limitViolation.getValue()) + .side(limitViolation.getSide()) + .build(); } } diff --git a/src/main/java/org/gridsuite/securityanalysis/server/entities/SecurityAnalysisResultEntity.java b/src/main/java/org/gridsuite/securityanalysis/server/entities/SecurityAnalysisResultEntity.java index 9f9f790b..4c20e32b 100644 --- a/src/main/java/org/gridsuite/securityanalysis/server/entities/SecurityAnalysisResultEntity.java +++ b/src/main/java/org/gridsuite/securityanalysis/server/entities/SecurityAnalysisResultEntity.java @@ -8,9 +8,7 @@ import com.powsybl.security.SecurityAnalysisResult; import jakarta.persistence.*; -import lombok.Getter; -import lombok.NoArgsConstructor; -import lombok.Setter; +import lombok.*; import org.gridsuite.securityanalysis.server.dto.SecurityAnalysisStatus; import org.gridsuite.securityanalysis.server.service.SecurityAnalysisResultService; @@ -27,6 +25,8 @@ @NoArgsConstructor @Getter @Entity +@AllArgsConstructor +@Builder @Table(name = "security_analysis_result") public class SecurityAnalysisResultEntity { @Id @@ -50,46 +50,6 @@ public SecurityAnalysisResultEntity(UUID id) { this.id = id; } - public SecurityAnalysisResultEntity(UUID id, SecurityAnalysisStatus status, String preContingencyStatus, List contingencies, List preContingencyLimitViolations) { - this.id = id; - this.status = status; - this.preContingencyStatus = preContingencyStatus; - setContingencies(contingencies); - setPreContingencyLimitViolations(preContingencyLimitViolations); - - // extracting unique subject limit violations from all limit violations - setSubjectLimitViolations( - Stream.concat( - this.contingencies.stream().flatMap(c -> c.getContingencyLimitViolations().stream()), - this.preContingencyLimitViolations.stream() - ).map(AbstractLimitViolationEntity::getSubjectLimitViolation) - .distinct() - .filter(Objects::nonNull) - .collect(Collectors.toList()) - ); - } - - private void setContingencies(List contingencies) { - if (contingencies != null) { - this.contingencies = contingencies; - this.contingencies.forEach(c -> c.setResult(this)); - } - } - - private void setPreContingencyLimitViolations(List preContingencyLimitViolations) { - if (preContingencyLimitViolations != null) { - this.preContingencyLimitViolations = preContingencyLimitViolations; - this.preContingencyLimitViolations.forEach(lm -> lm.setResult(this)); - } - } - - private void setSubjectLimitViolations(List subjectLimitViolations) { - if (subjectLimitViolations != null) { - this.subjectLimitViolations = subjectLimitViolations; - subjectLimitViolations.forEach(subjectLimitViolation -> subjectLimitViolation.setResult(this)); - } - } - public static SecurityAnalysisResultEntity toEntity(UUID resultUuid, SecurityAnalysisResult securityAnalysisResult, SecurityAnalysisStatus securityAnalysisStatus) { Map subjectLimitViolationsBySubjectId = SecurityAnalysisResultService.getUniqueSubjectLimitViolationsFromResult(securityAnalysisResult) .stream().collect(Collectors.toMap( @@ -102,6 +62,27 @@ public static SecurityAnalysisResultEntity toEntity(UUID resultUuid, SecurityAna List preContingencyLimitViolations = PreContingencyLimitViolationEntity.toEntityList(securityAnalysisResult.getPreContingencyResult(), subjectLimitViolationsBySubjectId); - return new SecurityAnalysisResultEntity(resultUuid, securityAnalysisStatus, securityAnalysisResult.getPreContingencyResult().getStatus().name(), contingencies, preContingencyLimitViolations); + List subjectLimitViolations = Stream.concat( + contingencies.stream().flatMap(c -> c.getContingencyLimitViolations().stream()), + preContingencyLimitViolations.stream() + ).map(AbstractLimitViolationEntity::getSubjectLimitViolation) + .distinct() + .filter(Objects::nonNull) + .collect(Collectors.toList()); + + SecurityAnalysisResultEntity securityAnalysisResultEntity = SecurityAnalysisResultEntity.builder() + .id(resultUuid) + .status(securityAnalysisStatus) + .preContingencyStatus(securityAnalysisResult.getPreContingencyResult().getStatus().name()) + .contingencies(contingencies) + .preContingencyLimitViolations(preContingencyLimitViolations) + .subjectLimitViolations(subjectLimitViolations) + .build(); + + //bidirectionnal associations + contingencies.forEach(c -> c.setResult(securityAnalysisResultEntity)); + preContingencyLimitViolations.forEach(lm -> lm.setResult(securityAnalysisResultEntity)); + subjectLimitViolations.forEach(subjectLimitViolation -> subjectLimitViolation.setResult(securityAnalysisResultEntity)); + return securityAnalysisResultEntity; } } diff --git a/src/main/java/org/gridsuite/securityanalysis/server/entities/SubjectLimitViolationEntity.java b/src/main/java/org/gridsuite/securityanalysis/server/entities/SubjectLimitViolationEntity.java index ddd0738e..88eba796 100644 --- a/src/main/java/org/gridsuite/securityanalysis/server/entities/SubjectLimitViolationEntity.java +++ b/src/main/java/org/gridsuite/securityanalysis/server/entities/SubjectLimitViolationEntity.java @@ -22,8 +22,9 @@ @Entity @Table(name = "subject_limit_violation") public class SubjectLimitViolationEntity { - public SubjectLimitViolationEntity(String subjectId) { + public SubjectLimitViolationEntity(String subjectId, String subjectName) { this.subjectId = subjectId; + this.subjectName = subjectName; } @Id @@ -33,6 +34,8 @@ public SubjectLimitViolationEntity(String subjectId) { @Getter public String subjectId; + private String subjectName; + @ManyToOne(fetch = FetchType.LAZY) @Setter @Getter diff --git a/src/main/java/org/gridsuite/securityanalysis/server/service/ActionsService.java b/src/main/java/org/gridsuite/securityanalysis/server/service/ActionsService.java index 0e97cf21..89d183b6 100644 --- a/src/main/java/org/gridsuite/securityanalysis/server/service/ActionsService.java +++ b/src/main/java/org/gridsuite/securityanalysis/server/service/ActionsService.java @@ -10,10 +10,13 @@ import org.springframework.beans.factory.annotation.Autowired; import org.springframework.beans.factory.annotation.Value; import org.springframework.core.ParameterizedTypeReference; +import org.springframework.http.HttpMethod; import org.springframework.stereotype.Service; -import org.springframework.web.reactive.function.client.WebClient; -import reactor.core.publisher.Flux; +import org.springframework.web.client.RestTemplate; +import org.springframework.web.util.UriComponentsBuilder; +import java.net.URI; +import java.util.List; import java.util.Objects; import java.util.Optional; import java.util.UUID; @@ -26,30 +29,33 @@ public class ActionsService { static final String ACTIONS_API_VERSION = "v1"; - private final WebClient webClient; + private static final String DELIMITER = "/"; - @Autowired - public ActionsService(WebClient.Builder builder, - @Value("${gridsuite.services.actions-server.base-uri:http://actions-server/}") String baseUri) { - webClient = builder.baseUrl(baseUri) - .build(); + private String baseUri; + + private RestTemplate restTemplate; + + public void setActionServiceBaseUri(String baseUri) { + this.baseUri = baseUri; } - public ActionsService(WebClient webClient) { - this.webClient = Objects.requireNonNull(webClient); + @Autowired + public ActionsService( + @Value("${gridsuite.services.actions-server.base-uri:http://actions-server/}") String baseUri, + RestTemplate restTemplate) { + this.baseUri = baseUri; + this.restTemplate = restTemplate; } - public Flux getContingencyList(String name, UUID networkUuid, String variantId) { + public List getContingencyList(String name, UUID networkUuid, String variantId) { Objects.requireNonNull(name); Objects.requireNonNull(networkUuid); - return webClient.get() - .uri(uriBuilder -> uriBuilder - .path(ACTIONS_API_VERSION + "/contingency-lists/{name}/export") - .queryParam("networkUuid", networkUuid.toString()) - .queryParamIfPresent("variantId", Optional.ofNullable(variantId)) - .build(name)) - .retrieve() - .bodyToFlux(new ParameterizedTypeReference<>() { - }); + + URI path = UriComponentsBuilder + .fromPath(DELIMITER + ACTIONS_API_VERSION + "/contingency-lists/{name}/export") + .queryParam("networkUuid", networkUuid.toString()) + .queryParamIfPresent("variantId", Optional.ofNullable(variantId)).build(name); + + return restTemplate.exchange(baseUri + path, HttpMethod.GET, null, new ParameterizedTypeReference>() { }).getBody(); } } diff --git a/src/main/java/org/gridsuite/securityanalysis/server/service/ReportService.java b/src/main/java/org/gridsuite/securityanalysis/server/service/ReportService.java index 176800b7..14b4f54e 100644 --- a/src/main/java/org/gridsuite/securityanalysis/server/service/ReportService.java +++ b/src/main/java/org/gridsuite/securityanalysis/server/service/ReportService.java @@ -10,10 +10,10 @@ import org.springframework.beans.factory.annotation.Autowired; import org.springframework.beans.factory.annotation.Value; import org.springframework.stereotype.Service; -import org.springframework.web.reactive.function.BodyInserters; -import org.springframework.web.reactive.function.client.WebClient; -import reactor.core.publisher.Mono; +import org.springframework.web.client.RestTemplate; +import org.springframework.web.util.UriComponentsBuilder; +import java.net.URI; import java.util.Objects; import java.util.UUID; @@ -25,27 +25,29 @@ public class ReportService { static final String REPORT_API_VERSION = "v1"; - private final WebClient webClient; + private static final String DELIMITER = "/"; + + private String baseUri; + + @Autowired + private RestTemplate restTemplate; @Autowired - public ReportService(WebClient.Builder builder, - @Value("${gridsuite.services.report-server.base-uri:http://report-server/}") String baseUri) { - webClient = builder.baseUrl(baseUri) - .build(); + public ReportService(@Value("${gridsuite.services.report-server.base-uri:http://report-server/}") String baseUri) { + this.baseUri = baseUri; } - public ReportService(WebClient webClient) { - this.webClient = Objects.requireNonNull(webClient); + public void setReportServiceBaseUri(String baseUri) { + this.baseUri = baseUri; } - public Mono sendReport(UUID reportUuid, Reporter reporter) { + public void sendReport(UUID reportUuid, Reporter reporter) { Objects.requireNonNull(reportUuid); - return webClient.put() - .uri(uriBuilder -> uriBuilder - .path(REPORT_API_VERSION + "/reports/{reportUuid}") - .build(reportUuid)) - .body(BodyInserters.fromValue(reporter)) - .retrieve() - .bodyToMono(Void.class); + + URI path = UriComponentsBuilder + .fromPath(DELIMITER + REPORT_API_VERSION + "/reports/{reportUuid}") + .build(reportUuid); + + restTemplate.put(baseUri + path, reporter); } } diff --git a/src/main/java/org/gridsuite/securityanalysis/server/service/SecurityAnalysisResultService.java b/src/main/java/org/gridsuite/securityanalysis/server/service/SecurityAnalysisResultService.java index e52d3b6a..a9156789 100644 --- a/src/main/java/org/gridsuite/securityanalysis/server/service/SecurityAnalysisResultService.java +++ b/src/main/java/org/gridsuite/securityanalysis/server/service/SecurityAnalysisResultService.java @@ -14,6 +14,7 @@ import org.gridsuite.securityanalysis.server.entities.*; import org.gridsuite.securityanalysis.server.repositories.*; import org.gridsuite.securityanalysis.server.util.SecurityAnalysisException; +import org.jgrapht.alg.util.Pair; import org.springframework.stereotype.Service; import org.springframework.transaction.annotation.Transactional; @@ -60,36 +61,18 @@ public PreContingencyResult findNResult(UUID resultUuid) { } @Transactional(readOnly = true) - public List findNmKContingenciesResult(UUID resultUuid) { + public List findNmKContingenciesResult(UUID resultUuid) { assertResultExists(resultUuid); List contingencies = contingencyRepository.findByResultIdAndStatusOrderByContingencyId(resultUuid, LoadFlowResult.ComponentResult.Status.CONVERGED.name()); - return contingencies.stream().map(contingency -> { - List subjectLimitViolations = contingency.getContingencyLimitViolations().stream() - .map(SubjectLimitViolationFromContingencyDTO::toDto) - .toList(); - return new ContingencyToSubjectLimitViolationDTO( - contingency.getContingencyId(), - contingency.getStatus(), - contingency.getContingencyElements().stream().map(ContingencyElementDTO::toDto).toList(), - subjectLimitViolations - ); - }).toList(); + return contingencies.stream().map(ContingencyResultDTO::toDto).toList(); } @Transactional(readOnly = true) - public List findNmKConstraintsResult(UUID resultUuid) { + public List findNmKConstraintsResult(UUID resultUuid) { assertResultExists(resultUuid); List subjectLimitViolations = subjectLimitViolationRepository.findByResultIdOrderBySubjectId(resultUuid); - return subjectLimitViolations.stream().map(subjectLimitViolation -> { - // we only keep converged contingencies here - List contingencies = subjectLimitViolation.getContingencyLimitViolations().stream() - .filter(lm -> LoadFlowResult.ComponentResult.Status.CONVERGED.name().equals(lm.getContingency().getStatus())) - .map(ContingencyFromSubjectLimitViolationDTO::toDto) - .toList(); - - return new SubjectLimitViolationToContingencyDTO(subjectLimitViolation.getSubjectId(), contingencies); - }).toList(); + return subjectLimitViolations.stream().map(SubjectLimitViolationResultDTO::toDto).toList(); } public void assertResultExists(UUID resultUuid) { @@ -143,10 +126,9 @@ public static List getUniqueSubjectLimitViolationsF return Stream.concat( securityAnalysisResult.getPostContingencyResults().stream().flatMap(pcr -> pcr.getLimitViolationsResult().getLimitViolations().stream()), securityAnalysisResult.getPreContingencyResult().getLimitViolationsResult().getLimitViolations().stream()) - .map(LimitViolation::getSubjectId) + .map(lm -> new Pair<>(lm.getSubjectId(), lm.getSubjectName())) .distinct() - .map(SubjectLimitViolationEntity::new) + .map(pair -> new SubjectLimitViolationEntity(pair.getFirst(), pair.getSecond())) .toList(); } - } diff --git a/src/main/java/org/gridsuite/securityanalysis/server/service/SecurityAnalysisService.java b/src/main/java/org/gridsuite/securityanalysis/server/service/SecurityAnalysisService.java index 8d26e57d..7561bcdb 100644 --- a/src/main/java/org/gridsuite/securityanalysis/server/service/SecurityAnalysisService.java +++ b/src/main/java/org/gridsuite/securityanalysis/server/service/SecurityAnalysisService.java @@ -9,12 +9,11 @@ import com.fasterxml.jackson.databind.ObjectMapper; import com.powsybl.security.SecurityAnalysisProvider; import com.powsybl.security.results.PreContingencyResult; -import org.gridsuite.securityanalysis.server.dto.SubjectLimitViolationToContingencyDTO; -import org.gridsuite.securityanalysis.server.dto.ContingencyToSubjectLimitViolationDTO; +import org.gridsuite.securityanalysis.server.dto.SubjectLimitViolationResultDTO; +import org.gridsuite.securityanalysis.server.dto.ContingencyResultDTO; import org.gridsuite.securityanalysis.server.dto.SecurityAnalysisStatus; import org.springframework.beans.factory.annotation.Value; import org.springframework.stereotype.Service; -import reactor.core.publisher.Mono; import java.util.List; import java.util.Objects; @@ -27,7 +26,7 @@ */ @Service public class SecurityAnalysisService { - private final SecurityAnalysisResultService resultRepository; + private final SecurityAnalysisResultService securityAnalysisResultService; private final UuidGeneratorService uuidGeneratorService; @@ -37,60 +36,58 @@ public class SecurityAnalysisService { private final String defaultProvider; - public SecurityAnalysisService(SecurityAnalysisResultService resultRepository, + public SecurityAnalysisService(SecurityAnalysisResultService securityAnalysisResultService, UuidGeneratorService uuidGeneratorService, ObjectMapper objectMapper, NotificationService notificationService, @Value("${security-analysis.default-provider}") String defaultProvider) { - this.resultRepository = Objects.requireNonNull(resultRepository); + this.securityAnalysisResultService = Objects.requireNonNull(securityAnalysisResultService); this.uuidGeneratorService = Objects.requireNonNull(uuidGeneratorService); this.objectMapper = Objects.requireNonNull(objectMapper); this.notificationService = Objects.requireNonNull(notificationService); this.defaultProvider = Objects.requireNonNull(defaultProvider); } - public Mono runAndSaveResult(SecurityAnalysisRunContext runContext) { + public UUID runAndSaveResult(SecurityAnalysisRunContext runContext) { Objects.requireNonNull(runContext); var resultUuid = uuidGeneratorService.generate(); - // update status to running status - return setStatus(List.of(resultUuid), SecurityAnalysisStatus.RUNNING).then( - Mono.fromRunnable(() -> - notificationService.emitRunAnalysisMessage(new SecurityAnalysisResultContext(resultUuid, runContext).toMessage(objectMapper)) - ) - .thenReturn(resultUuid)); + setStatus(List.of(resultUuid), SecurityAnalysisStatus.RUNNING); + notificationService.emitRunAnalysisMessage(new SecurityAnalysisResultContext(resultUuid, runContext).toMessage(objectMapper)); + + return resultUuid; } - public Mono getNResult(UUID resultUuid) { - return Mono.fromCallable(() -> resultRepository.findNResult(resultUuid)); + public PreContingencyResult getNResult(UUID resultUuid) { + return securityAnalysisResultService.findNResult(resultUuid); } - public Mono> getNmKContingenciesResult(UUID resultUuid) { - return Mono.fromCallable(() -> resultRepository.findNmKContingenciesResult(resultUuid)); + public List getNmKContingenciesResult(UUID resultUuid) { + return securityAnalysisResultService.findNmKContingenciesResult(resultUuid); } - public Mono> getNmKConstraintsResult(UUID resultUuid) { - return Mono.fromCallable(() -> resultRepository.findNmKConstraintsResult(resultUuid)); + public List getNmKConstraintsResult(UUID resultUuid) { + return securityAnalysisResultService.findNmKConstraintsResult(resultUuid); } - public Mono deleteResult(UUID resultUuid) { - return Mono.fromRunnable(() -> resultRepository.delete(resultUuid)); + public void deleteResult(UUID resultUuid) { + securityAnalysisResultService.delete(resultUuid); } - public Mono deleteResults() { - return Mono.fromRunnable(() -> resultRepository.deleteAll()); + public void deleteResults() { + securityAnalysisResultService.deleteAll(); } - public Mono getStatus(UUID resultUuid) { - return Mono.fromCallable(() -> resultRepository.findStatus(resultUuid)); + public SecurityAnalysisStatus getStatus(UUID resultUuid) { + return securityAnalysisResultService.findStatus(resultUuid); } - public Mono setStatus(List resultUuids, SecurityAnalysisStatus status) { - return Mono.fromRunnable(() -> resultRepository.insertStatus(resultUuids, status)); + public void setStatus(List resultUuids, SecurityAnalysisStatus status) { + securityAnalysisResultService.insertStatus(resultUuids, status); } - public Mono stop(UUID resultUuid, String receiver) { - return Mono.fromRunnable(() -> notificationService.emitCancelAnalysisMessage(new SecurityAnalysisCancelContext(resultUuid, receiver).toMessage())).then(); + public void stop(UUID resultUuid, String receiver) { + notificationService.emitCancelAnalysisMessage(new SecurityAnalysisCancelContext(resultUuid, receiver).toMessage()); } public List getProviders() { diff --git a/src/main/java/org/gridsuite/securityanalysis/server/service/SecurityAnalysisWorkerService.java b/src/main/java/org/gridsuite/securityanalysis/server/service/SecurityAnalysisWorkerService.java index c625de08..17fa844f 100644 --- a/src/main/java/org/gridsuite/securityanalysis/server/service/SecurityAnalysisWorkerService.java +++ b/src/main/java/org/gridsuite/securityanalysis/server/service/SecurityAnalysisWorkerService.java @@ -33,10 +33,6 @@ import org.springframework.messaging.Message; import org.springframework.stereotype.Service; import org.springframework.web.server.ResponseStatusException; -import reactor.core.publisher.Flux; -import reactor.core.publisher.Mono; -import reactor.core.scheduler.Schedulers; -import reactor.util.function.Tuple2; import java.util.ArrayList; import java.util.Collections; @@ -45,10 +41,7 @@ import java.util.Objects; import java.util.Set; import java.util.UUID; -import java.util.concurrent.CancellationException; -import java.util.concurrent.CompletableFuture; -import java.util.concurrent.ConcurrentHashMap; -import java.util.concurrent.TimeUnit; +import java.util.concurrent.*; import java.util.concurrent.atomic.AtomicReference; import java.util.concurrent.locks.Lock; import java.util.concurrent.locks.ReentrantLock; @@ -111,46 +104,43 @@ public void setSecurityAnalysisFactorySupplier(Function getNetwork(UUID networkUuid) { - // FIXME to re-implement when network store service will be reactive - return Mono.fromCallable(() -> { - try { - return networkStoreService.getNetwork(networkUuid, PreloadingStrategy.COLLECTION); - } catch (PowsyblException e) { - throw new ResponseStatusException(HttpStatus.NOT_FOUND, e.getMessage()); - } - }) - .subscribeOn(Schedulers.boundedElastic()); + private Network getNetwork(UUID networkUuid) { + try { + return networkStoreService.getNetwork(networkUuid, PreloadingStrategy.COLLECTION); + } catch (PowsyblException e) { + throw new ResponseStatusException(HttpStatus.NOT_FOUND, e.getMessage()); + } } - private Mono getNetwork(UUID networkUuid, List otherNetworkUuids) { - Mono network = getNetwork(networkUuid); + private Network getNetwork(UUID networkUuid, List otherNetworkUuids) { + Network network = getNetwork(networkUuid); if (otherNetworkUuids.isEmpty()) { return network; } else { - Mono> otherNetworks = Flux.fromIterable(otherNetworkUuids) - .flatMap(this::getNetwork) - .collectList(); - return Mono.zip(network, otherNetworks) - .map(t -> { - // creation of the merging view - List networks = new ArrayList<>(); - networks.add(t.getT1()); - networks.addAll(t.getT2()); - MergingView mergingView = MergingView.create("merge", "iidm"); - mergingView.merge(networks.toArray(new Network[0])); - return mergingView; - }); + List networks = new ArrayList<>(); + List otherNetworks = otherNetworkUuids + .stream() + .map(this::getNetwork) + .collect(Collectors.toList()); + + networks.add(network); + networks.addAll(otherNetworks); + + MergingView mergingView = MergingView.create("merge", "iidm"); + mergingView.merge(networks.toArray(new Network[0])); + + return mergingView; } } - public Mono run(SecurityAnalysisRunContext context) { + public SecurityAnalysisResult run(SecurityAnalysisRunContext context) { return run(context, null); } private CompletableFuture runASAsync(SecurityAnalysisRunContext context, SecurityAnalysis.Runner securityAnalysisRunner, - Tuple2> tuple, + Network network, + List contingencies, Reporter reporter, UUID resultUuid) { lockRunAndCancelAS.lock(); @@ -161,9 +151,9 @@ private CompletableFuture runASAsync(SecurityAnalysisRun String variantId = context.getVariantId() != null ? context.getVariantId() : VariantManagerConstants.INITIAL_VARIANT_ID; CompletableFuture future = securityAnalysisRunner.runAsync( - tuple.getT1(), + network, variantId, - n -> tuple.getT2(), + n -> contingencies, context.getParameters(), securityAnalysisExecutionService.getLocalComputationManager(), LimitViolationFilter.load(), @@ -206,95 +196,87 @@ private void cleanASResultsAndPublishCancel(UUID resultUuid, String receiver) { LOGGER.info(CANCEL_MESSAGE + " (resultUuid='{}')", resultUuid); } - private Mono run(SecurityAnalysisRunContext context, UUID resultUuid) { + private SecurityAnalysisResult run(SecurityAnalysisRunContext context, UUID resultUuid) { Objects.requireNonNull(context); LOGGER.info("Run security analysis on contingency lists: {}", context.getContingencyListNames().stream().map(LogUtils::sanitizeParam).collect(Collectors.toList())); - Mono network = getNetwork(context.getNetworkUuid(), context.getOtherNetworkUuids()); - - Mono> contingencies = Flux.fromIterable(context.getContingencyListNames()) - .flatMap(contingencyListName -> actionsService.getContingencyList(contingencyListName, context.getNetworkUuid(), context.getVariantId())) - .collectList(); + Network network = getNetwork(context.getNetworkUuid(), context.getOtherNetworkUuids()); - return Mono.zip(network, contingencies) - .flatMap(tuple -> { + List contingencies = context.getContingencyListNames().stream() + .map(contingencyListName -> actionsService.getContingencyList(contingencyListName, context.getNetworkUuid(), context.getVariantId())) + .flatMap(List::stream) + .collect(Collectors.toList()); - SecurityAnalysis.Runner securityAnalysisRunner = securityAnalysisFactorySupplier.apply(context.getProvider()); + SecurityAnalysis.Runner securityAnalysisRunner = securityAnalysisFactorySupplier.apply(context.getProvider()); - Reporter rootReporter = Reporter.NO_OP; - Reporter reporter = Reporter.NO_OP; - if (context.getReportUuid() != null) { - String rootReporterId = context.getReporterId() == null ? AS_TYPE_REPORT : context.getReporterId() + "@" + AS_TYPE_REPORT; - rootReporter = new ReporterModel(rootReporterId, rootReporterId); - reporter = rootReporter.createSubReporter(AS_TYPE_REPORT, AS_TYPE_REPORT + " (${providerToUse})", "providerToUse", securityAnalysisRunner.getName()); - } + Reporter rootReporter = Reporter.NO_OP; + Reporter reporter = Reporter.NO_OP; + if (context.getReportUuid() != null) { + String rootReporterId = context.getReporterId() == null ? AS_TYPE_REPORT : context.getReporterId() + "@" + AS_TYPE_REPORT; + rootReporter = new ReporterModel(rootReporterId, rootReporterId); + reporter = rootReporter.createSubReporter(AS_TYPE_REPORT, AS_TYPE_REPORT + " (${providerToUse})", "providerToUse", securityAnalysisRunner.getName()); + } - CompletableFuture future = runASAsync(context, securityAnalysisRunner, tuple, reporter, resultUuid); + CompletableFuture future = runASAsync(context, securityAnalysisRunner, network, contingencies, reporter, resultUuid); - Mono result = future == null ? Mono.empty() : Mono.fromCompletionStage(future); - if (context.getReportUuid() != null) { - Reporter finalRootReporter = rootReporter; - return result.zipWhen(r -> reportService.sendReport(context.getReportUuid(), finalRootReporter) - .thenReturn("") /* because zipWhen needs 2 non empty mono */) - .map(Tuple2::getT1); - } else { - return result; - } - }); + SecurityAnalysisResult result; + try { + result = future == null ? null : future.get(); + } catch (CancellationException | InterruptedException | ExecutionException e) { + Thread.currentThread().interrupt(); + throw new CancellationException(e.getMessage()); + } + if (context.getReportUuid() != null) { + Reporter finalRootReporter = rootReporter; + reportService.sendReport(context.getReportUuid(), finalRootReporter); + } + return result; } @Bean public Consumer> consumeRun() { return message -> { + SecurityAnalysisResultContext resultContext = SecurityAnalysisResultContext.fromMessage(message, objectMapper); try { - SecurityAnalysisResultContext resultContext = SecurityAnalysisResultContext.fromMessage(message, objectMapper); runRequests.add(resultContext.getResultUuid()); AtomicReference startTime = new AtomicReference<>(); - run(resultContext.getRunContext(), resultContext.getResultUuid()) - .doOnSubscribe(x -> startTime.set(System.nanoTime())) - .flatMap(result -> { - long nanoTime = System.nanoTime(); - LOGGER.info("Just run in {}s", TimeUnit.NANOSECONDS.toSeconds(nanoTime - startTime.getAndSet(nanoTime))); - return Mono.fromRunnable(() -> securityAnalysisResultService.insert(resultContext.getResultUuid(), - result, - result.getPreContingencyResult().getStatus() == LoadFlowResult.ComponentResult.Status.CONVERGED ? SecurityAnalysisStatus.CONVERGED : SecurityAnalysisStatus.DIVERGED)) - .then(Mono.just(result)) - .doFinally(ignored -> { - long finalNanoTime = System.nanoTime(); - LOGGER.info("Stored in {}s", TimeUnit.NANOSECONDS.toSeconds(finalNanoTime - startTime.getAndSet(finalNanoTime))); - }); - }) - .doOnSuccess(result -> { - if (result != null) { // result available - notificationService.emitAnalysisResultsMessage(resultContext.getResultUuid().toString(), resultContext.getRunContext().getReceiver()); - LOGGER.info("Security analysis complete (resultUuid='{}')", resultContext.getResultUuid()); - } else { // result not available : stop computation request - if (cancelComputationRequests.get(resultContext.getResultUuid()) != null) { - cleanASResultsAndPublishCancel(resultContext.getResultUuid(), cancelComputationRequests.get(resultContext.getResultUuid()).getReceiver()); - } - } - }) - .onErrorResume(throwable -> { - if (!(throwable instanceof CancellationException)) { - LOGGER.error(FAIL_MESSAGE, throwable); - notificationService.emitFailAnalysisMessage(resultContext.getResultUuid().toString(), - resultContext.getRunContext().getReceiver(), - throwable.getMessage()); - securityAnalysisResultService.delete(resultContext.getResultUuid()); - return Mono.empty(); - } - return Mono.empty(); - }) - .doFinally(s -> { - futures.remove(resultContext.getResultUuid()); - cancelComputationRequests.remove(resultContext.getResultUuid()); - runRequests.remove(resultContext.getResultUuid()); - }) - .block(); + startTime.set(System.nanoTime()); + SecurityAnalysisResult result = run(resultContext.getRunContext(), resultContext.getResultUuid()); + long nanoTime = System.nanoTime(); + LOGGER.info("Just run in {}s", TimeUnit.NANOSECONDS.toSeconds(nanoTime - startTime.getAndSet(nanoTime))); + + securityAnalysisResultService.insert( + resultContext.getResultUuid(), + result, + result.getPreContingencyResult().getStatus() == LoadFlowResult.ComponentResult.Status.CONVERGED + ? SecurityAnalysisStatus.CONVERGED + : SecurityAnalysisStatus.DIVERGED); + + long finalNanoTime = System.nanoTime(); + LOGGER.info("Stored in {}s", TimeUnit.NANOSECONDS.toSeconds(finalNanoTime - startTime.getAndSet(finalNanoTime))); + + if (result != null) { // result available + notificationService.emitAnalysisResultsMessage(resultContext.getResultUuid().toString(), resultContext.getRunContext().getReceiver()); + LOGGER.info("Security analysis complete (resultUuid='{}')", resultContext.getResultUuid()); + } else { // result not available : stop computation request + if (cancelComputationRequests.get(resultContext.getResultUuid()) != null) { + cleanASResultsAndPublishCancel(resultContext.getResultUuid(), cancelComputationRequests.get(resultContext.getResultUuid()).getReceiver()); + } + } } catch (Exception e) { - LOGGER.error("Exception in consumeRun", e); + if (!(e instanceof CancellationException)) { + LOGGER.error(FAIL_MESSAGE, e); + notificationService.emitFailAnalysisMessage(resultContext.getResultUuid().toString(), + resultContext.getRunContext().getReceiver(), + e.getMessage()); + securityAnalysisResultService.delete(resultContext.getResultUuid()); + } + } finally { + futures.remove(resultContext.getResultUuid()); + cancelComputationRequests.remove(resultContext.getResultUuid()); + runRequests.remove(resultContext.getResultUuid()); } }; } diff --git a/src/main/resources/db/changelog/changesets/changelog_20231012T095523Z.xml b/src/main/resources/db/changelog/changesets/changelog_20231012T095523Z.xml index 3ab7c0e4..f60bb5f4 100644 --- a/src/main/resources/db/changelog/changesets/changelog_20231012T095523Z.xml +++ b/src/main/resources/db/changelog/changesets/changelog_20231012T095523Z.xml @@ -15,7 +15,6 @@ - @@ -36,7 +35,6 @@ - @@ -57,6 +55,7 @@ + diff --git a/src/test/java/org/gridsuite/securityanalysis/server/SecurityAnalysisControllerTest.java b/src/test/java/org/gridsuite/securityanalysis/server/SecurityAnalysisControllerTest.java index c3fde1ed..2b577335 100644 --- a/src/test/java/org/gridsuite/securityanalysis/server/SecurityAnalysisControllerTest.java +++ b/src/test/java/org/gridsuite/securityanalysis/server/SecurityAnalysisControllerTest.java @@ -6,6 +6,7 @@ */ package org.gridsuite.securityanalysis.server; +import com.fasterxml.jackson.core.type.TypeReference; import com.fasterxml.jackson.databind.ObjectMapper; import com.powsybl.commons.reporter.Reporter; import com.powsybl.iidm.network.Network; @@ -20,9 +21,15 @@ import com.powsybl.security.SecurityAnalysisResult; import com.powsybl.security.results.PreContingencyResult; import lombok.SneakyThrows; +import org.gridsuite.securityanalysis.server.dto.ContingencyResultDTO; import org.gridsuite.securityanalysis.server.dto.SecurityAnalysisParametersInfos; import org.gridsuite.securityanalysis.server.dto.SecurityAnalysisStatus; -import org.gridsuite.securityanalysis.server.service.*; +import org.gridsuite.securityanalysis.server.dto.SubjectLimitViolationResultDTO; +import org.gridsuite.securityanalysis.server.service.ActionsService; +import org.gridsuite.securityanalysis.server.service.ReportService; +import org.gridsuite.securityanalysis.server.service.SecurityAnalysisWorkerService; +import org.gridsuite.securityanalysis.server.service.UuidGeneratorService; +import org.gridsuite.securityanalysis.server.util.ContextConfigurationWithTestChannel; import org.gridsuite.securityanalysis.server.util.MatcherJson; import org.junit.After; import org.junit.Before; @@ -31,44 +38,45 @@ import org.mockito.MockitoAnnotations; import org.mockito.stubbing.Answer; import org.springframework.beans.factory.annotation.Autowired; -import org.springframework.boot.test.autoconfigure.web.reactive.AutoConfigureWebTestClient; +import org.springframework.boot.test.autoconfigure.web.servlet.AutoConfigureMockMvc; import org.springframework.boot.test.context.SpringBootTest; import org.springframework.boot.test.mock.mockito.MockBean; import org.springframework.cloud.stream.binder.test.OutputDestination; -import org.springframework.cloud.stream.binder.test.TestChannelBinderConfiguration; import org.springframework.http.MediaType; import org.springframework.messaging.Message; -import org.springframework.test.context.ContextConfiguration; -import org.springframework.test.context.ContextHierarchy; import org.springframework.test.context.junit4.SpringRunner; -import org.springframework.test.web.reactive.server.WebTestClient; -import org.springframework.web.reactive.config.EnableWebFlux; -import reactor.core.publisher.Flux; -import reactor.core.publisher.Mono; +import org.springframework.test.web.servlet.MockMvc; +import org.springframework.test.web.servlet.MvcResult; import java.lang.reflect.Constructor; import java.nio.charset.StandardCharsets; import java.util.List; import java.util.Map; import java.util.UUID; +import java.util.concurrent.CountDownLatch; import static com.powsybl.network.store.model.NetworkStoreApi.VERSION; import static org.gridsuite.securityanalysis.server.SecurityAnalysisProviderMock.*; import static org.gridsuite.securityanalysis.server.service.NotificationService.CANCEL_MESSAGE; import static org.gridsuite.securityanalysis.server.service.NotificationService.FAIL_MESSAGE; +import static org.hamcrest.MatcherAssert.assertThat; import static org.junit.Assert.assertEquals; import static org.mockito.ArgumentMatchers.any; import static org.mockito.BDDMockito.given; +import static org.mockito.Mockito.doNothing; import static org.mockito.Mockito.when; +import static org.springframework.test.web.servlet.request.MockMvcRequestBuilders.*; +import static org.springframework.test.web.servlet.result.MockMvcResultMatchers.content; +import static org.springframework.test.web.servlet.result.MockMvcResultMatchers.status; /** * @author Geoffroy Jamgotchian */ + @RunWith(SpringRunner.class) -@AutoConfigureWebTestClient -@EnableWebFlux +@AutoConfigureMockMvc @SpringBootTest -@ContextHierarchy({@ContextConfiguration(classes = {SecurityAnalysisApplication.class, TestChannelBinderConfiguration.class})}) +@ContextConfigurationWithTestChannel public class SecurityAnalysisControllerTest { private static final UUID NETWORK_UUID = UUID.fromString("7928181c-7977-4592-ba19-88027e4254e4"); @@ -87,7 +95,7 @@ public class SecurityAnalysisControllerTest { private OutputDestination output; @Autowired - private WebTestClient webTestClient; + private MockMvc mockMvc; @MockBean private NetworkStoreService networkStoreService; @@ -127,39 +135,39 @@ public void setUp() throws Exception { when(networkStoreService.getNetwork(NETWORK_STOP_UUID, PreloadingStrategy.COLLECTION)).thenAnswer((Answer) invocation -> { //Needed so the stop call doesn't arrive too late - Thread.sleep(2000); Network network1 = new NetworkFactoryImpl().createNetwork("other", "test"); - network1.getVariantManager().cloneVariant(VariantManagerConstants.INITIAL_VARIANT_ID, VARIANT_2_ID); + network1.getVariantManager().cloneVariant(VariantManagerConstants.INITIAL_VARIANT_ID, VARIANT_TO_STOP_ID); return network1; }); // action service mocking given(actionsService.getContingencyList(CONTINGENCY_LIST_NAME, NETWORK_UUID, VARIANT_1_ID)) - .willReturn(Flux.fromIterable(SecurityAnalysisProviderMock.CONTINGENCIES)); + .willReturn(SecurityAnalysisProviderMock.CONTINGENCIES); given(actionsService.getContingencyList(CONTINGENCY_LIST_NAME_VARIANT, NETWORK_UUID, VARIANT_3_ID)) - .willReturn(Flux.fromIterable(SecurityAnalysisProviderMock.CONTINGENCIES_VARIANT)); + .willReturn(SecurityAnalysisProviderMock.CONTINGENCIES_VARIANT); given(actionsService.getContingencyList(CONTINGENCY_LIST_NAME, NETWORK_UUID, VARIANT_2_ID)) - .willReturn(Flux.fromIterable(SecurityAnalysisProviderMock.CONTINGENCIES)); + .willReturn(SecurityAnalysisProviderMock.CONTINGENCIES); given(actionsService.getContingencyList(CONTINGENCY_LIST_NAME, NETWORK_UUID, null)) - .willReturn(Flux.fromIterable(SecurityAnalysisProviderMock.CONTINGENCIES)); + .willReturn(SecurityAnalysisProviderMock.CONTINGENCIES); given(actionsService.getContingencyList(CONTINGENCY_LIST2_NAME, NETWORK_UUID, VARIANT_1_ID)) - .willReturn(Flux.fromIterable(SecurityAnalysisProviderMock.CONTINGENCIES)); + .willReturn(SecurityAnalysisProviderMock.CONTINGENCIES); given(actionsService.getContingencyList(CONTINGENCY_LIST_NAME, NETWORK_STOP_UUID, VARIANT_2_ID)) - .willReturn(Flux.fromIterable(SecurityAnalysisProviderMock.CONTINGENCIES)); + .willReturn(SecurityAnalysisProviderMock.CONTINGENCIES); given(actionsService.getContingencyList(CONTINGENCY_LIST2_NAME, NETWORK_STOP_UUID, VARIANT_2_ID)) - .willReturn(Flux.fromIterable(SecurityAnalysisProviderMock.CONTINGENCIES)); + .willReturn(SecurityAnalysisProviderMock.CONTINGENCIES); given(actionsService.getContingencyList(CONTINGENCY_LIST_ERROR_NAME, NETWORK_UUID, VARIANT_1_ID)) - .willReturn(Flux.fromIterable(SecurityAnalysisProviderMock.CONTINGENCIES).thenMany(Flux.error(new RuntimeException(ERROR_MESSAGE)))); + .willReturn(SecurityAnalysisProviderMock.CONTINGENCIES); + given(actionsService.getContingencyList(CONTINGENCY_LIST_NAME, NETWORK_STOP_UUID, VARIANT_TO_STOP_ID)) + .willReturn(SecurityAnalysisProviderMock.CONTINGENCIES); given(actionsService.getContingencyList(CONTINGENCY_LIST_NAME, NETWORK_FOR_MERGING_VIEW_UUID, null)) - .willReturn(Flux.fromIterable(SecurityAnalysisProviderMock.CONTINGENCIES)); + .willReturn(SecurityAnalysisProviderMock.CONTINGENCIES); given(actionsService.getContingencyList(CONTINGENCY_LIST_NAME, OTHER_NETWORK_FOR_MERGING_VIEW_UUID, null)) - .willReturn(Flux.fromIterable(SecurityAnalysisProviderMock.CONTINGENCIES)); + .willReturn(SecurityAnalysisProviderMock.CONTINGENCIES); // UUID service mocking to always generate the same result UUID given(uuidGeneratorService.generate()).willReturn(RESULT_UUID); - given(reportService.sendReport(any(UUID.class), any(Reporter.class))) - .willReturn(Mono.empty()); + doNothing().when(reportService).sendReport(any(UUID.class), any(Reporter.class)); // SecurityAnalysis.Runner constructor is private.. Constructor constructor = SecurityAnalysis.Runner.class.getDeclaredConstructor(SecurityAnalysisProvider.class); @@ -184,22 +192,23 @@ public void setUp() throws Exception { // added for testStatus can return null, after runTest @After - public void tearDown() { - webTestClient.delete().uri("/" + VERSION + "/results") - .exchange() - .expectStatus().isOk(); + public void tearDown() throws Exception { + mockMvc.perform(delete("/" + VERSION + "/results")) + .andExpect(status().isOk()); } @SneakyThrows public void simpleRunRequest(SecurityAnalysisParametersInfos lfParams) { - webTestClient.post() - .uri("/" + VERSION + "/networks/" + NETWORK_UUID + "/run?contingencyListName=" + CONTINGENCY_LIST_NAME_VARIANT + "&variantId=" + VARIANT_3_ID) - .bodyValue(lfParams) - .exchange() - .expectStatus().isOk() - .expectHeader().contentType(MediaType.APPLICATION_JSON) - .expectBody(SecurityAnalysisResult.class) - .value(new MatcherJson<>(mapper, RESULT_VARIANT)); + MvcResult mvcResult = mockMvc.perform(post("/" + VERSION + "/networks/" + NETWORK_UUID + "/run?contingencyListName=" + CONTINGENCY_LIST_NAME_VARIANT + "&variantId=" + VARIANT_3_ID) + .contentType(MediaType.APPLICATION_JSON) + .content(mapper.writeValueAsString(lfParams))) + .andExpectAll( + status().isOk(), + content().contentType(MediaType.APPLICATION_JSON)).andReturn(); + String resultAsString = mvcResult.getResponse().getContentAsString(); + SecurityAnalysisResult securityAnalysisResult = mapper.readValue(resultAsString, SecurityAnalysisResult.class); + + assertThat(RESULT_VARIANT, new MatcherJson<>(mapper, securityAnalysisResult)); } @Test @@ -219,190 +228,238 @@ public void runTestWithLFParams() { } @Test - public void runTest() { + public void runTest() throws Exception { + MvcResult mvcResult; + String resultAsString; + // run with specific variant - webTestClient.post() - .uri("/" + VERSION + "/networks/" + NETWORK_UUID + "/run?contingencyListName=" + CONTINGENCY_LIST_NAME_VARIANT + "&variantId=" + VARIANT_3_ID + "&provider=OpenLoadFlow") - .exchange() - .expectStatus().isOk() - .expectHeader().contentType(MediaType.APPLICATION_JSON) - .expectBody(SecurityAnalysisResult.class) - .value(new MatcherJson<>(mapper, RESULT_VARIANT)); + mvcResult = mockMvc.perform(post("/" + VERSION + "/networks/" + NETWORK_UUID + "/run?contingencyListName=" + CONTINGENCY_LIST_NAME_VARIANT + "&variantId=" + VARIANT_3_ID + "&provider=OpenLoadFlow")) + .andExpectAll( + status().isOk(), + content().contentType(MediaType.APPLICATION_JSON) + ).andReturn(); + + resultAsString = mvcResult.getResponse().getContentAsString(); + SecurityAnalysisResult securityAnalysisResult = mapper.readValue(resultAsString, SecurityAnalysisResult.class); + assertThat(RESULT_VARIANT, new MatcherJson<>(mapper, securityAnalysisResult)); // run with implicit initial variant - webTestClient.post() - .uri("/" + VERSION + "/networks/" + NETWORK_UUID + "/run?contingencyListName=" + CONTINGENCY_LIST_NAME) - .exchange() - .expectStatus().isOk() - .expectHeader().contentType(MediaType.APPLICATION_JSON) - .expectBody(SecurityAnalysisResult.class) - .value(new MatcherJson<>(mapper, RESULT)); + mvcResult = mockMvc.perform(post("/" + VERSION + "/networks/" + NETWORK_UUID + "/run?contingencyListName=" + CONTINGENCY_LIST_NAME)) + .andExpectAll( + status().isOk(), + content().contentType(MediaType.APPLICATION_JSON) + ).andReturn(); + + resultAsString = mvcResult.getResponse().getContentAsString(); + securityAnalysisResult = mapper.readValue(resultAsString, SecurityAnalysisResult.class); + assertThat(RESULT, new MatcherJson<>(mapper, securityAnalysisResult)); } @Test - public void runAndSaveTest() { - webTestClient.post() - .uri("/" + VERSION + "/networks/" + NETWORK_UUID + "/run-and-save?contingencyListName=" + CONTINGENCY_LIST_NAME - + "&receiver=me&variantId=" + VARIANT_2_ID + "&provider=OpenLoadFlow") - .exchange() - .expectStatus().isOk() - .expectHeader().contentType(MediaType.APPLICATION_JSON) - .expectBody(UUID.class) - .isEqualTo(RESULT_UUID); + public void runAndSaveTest() throws Exception { + MvcResult mvcResult; + String resultAsString; + + mvcResult = mockMvc.perform(post("/" + VERSION + "/networks/" + NETWORK_UUID + "/run-and-save?contingencyListName=" + CONTINGENCY_LIST_NAME + + "&receiver=me&variantId=" + VARIANT_2_ID + "&provider=OpenLoadFlow")) + .andExpectAll( + status().isOk(), + content().contentType(MediaType.APPLICATION_JSON) + ).andReturn(); + + resultAsString = mvcResult.getResponse().getContentAsString(); + UUID resultUuid = mapper.readValue(resultAsString, UUID.class); + assertEquals(RESULT_UUID, resultUuid); Message resultMessage = output.receive(TIMEOUT, "sa.result"); assertEquals(RESULT_UUID.toString(), resultMessage.getHeaders().get("resultUuid")); assertEquals("me", resultMessage.getHeaders().get("receiver")); - webTestClient.get() - .uri("/" + VERSION + "/results/" + RESULT_UUID + "/n-result") - .exchange() - .expectStatus().isOk() - .expectHeader().contentType(MediaType.APPLICATION_JSON) - .expectBody(PreContingencyResult.class) - .value(new MatcherJson<>(mapper, RESULT.getPreContingencyResult())); - - webTestClient.get() - .uri("/" + VERSION + "/results/" + RESULT_UUID + "/nmk-contingencies-result") - .exchange() - .expectStatus().isOk() - .expectHeader().contentType(MediaType.APPLICATION_JSON) - .expectBody(List.class) - .value(new MatcherJson<>(mapper, RESULT_CONTINGENCIES)); - - webTestClient.get() - .uri("/" + VERSION + "/results/" + RESULT_UUID + "/nmk-constraints-result") - .exchange() - .expectStatus().isOk() - .expectHeader().contentType(MediaType.APPLICATION_JSON) - .expectBody(List.class) - .value(new MatcherJson<>(mapper, RESULT_CONSTRAINTS)); + mvcResult = mockMvc.perform(get("/" + VERSION + "/results/" + RESULT_UUID + "/n-result")) + .andExpectAll( + status().isOk(), + content().contentType(MediaType.APPLICATION_JSON) + ).andReturn(); + + resultAsString = mvcResult.getResponse().getContentAsString(); + PreContingencyResult preContingencyResult = mapper.readValue(resultAsString, PreContingencyResult.class); + assertThat(RESULT.getPreContingencyResult(), new MatcherJson<>(mapper, preContingencyResult)); + + mvcResult = mockMvc.perform(get("/" + VERSION + "/results/" + RESULT_UUID + "/nmk-contingencies-result")) + .andExpectAll( + status().isOk(), + content().contentType(MediaType.APPLICATION_JSON) + ).andReturn(); + + resultAsString = mvcResult.getResponse().getContentAsString(); + List contingenciesToConstraints = mapper.readValue(resultAsString, new TypeReference>() { }); + assertThat(RESULT_CONTINGENCIES, new MatcherJson<>(mapper, contingenciesToConstraints)); + + mvcResult = mockMvc.perform(get("/" + VERSION + "/results/" + RESULT_UUID + "/nmk-constraints-result")) + .andExpectAll( + status().isOk(), + content().contentType(MediaType.APPLICATION_JSON) + ).andReturn(); + + resultAsString = mvcResult.getResponse().getContentAsString(); + List constraintsToContingencies = mapper.readValue(resultAsString, new TypeReference>() { }); + assertThat(RESULT_CONSTRAINTS, new MatcherJson<>(mapper, constraintsToContingencies)); // should throw not found if result does not exist assertResultNotFound(OTHER_RESULT_UUID); // test one result deletion - webTestClient.delete() - .uri("/" + VERSION + "/results/" + RESULT_UUID) - .exchange() - .expectStatus().isOk(); + mockMvc.perform(delete("/" + VERSION + "/results/" + RESULT_UUID)) + .andExpect(status().isOk()); assertResultNotFound(RESULT_UUID); } @Test - public void runWithTwoLists() { - webTestClient.post() - .uri("/" + VERSION + "/networks/" + NETWORK_UUID + "/run?contingencyListName=" + CONTINGENCY_LIST_NAME + - "&contingencyListName=" + CONTINGENCY_LIST2_NAME + "&variantId=" + VARIANT_1_ID) - .exchange() - .expectStatus().isOk() - .expectHeader().contentType(MediaType.APPLICATION_JSON) - .expectBody(SecurityAnalysisResult.class) - .value(new MatcherJson<>(mapper, RESULT)); + public void runWithTwoLists() throws Exception { + MvcResult mvcResult; + String resultAsString; + + mvcResult = mockMvc.perform(post("/" + VERSION + "/networks/" + NETWORK_UUID + "/run?contingencyListName=" + CONTINGENCY_LIST_NAME + + "&contingencyListName=" + CONTINGENCY_LIST2_NAME + "&variantId=" + VARIANT_1_ID)) + .andExpectAll( + status().isOk(), + content().contentType(MediaType.APPLICATION_JSON) + ).andReturn(); + resultAsString = mvcResult.getResponse().getContentAsString(); + SecurityAnalysisResult securityAnalysisResult = mapper.readValue(resultAsString, SecurityAnalysisResult.class); + assertThat(RESULT, new MatcherJson<>(mapper, securityAnalysisResult)); } @Test - public void deleteResultsTest() { - webTestClient.post() - .uri("/" + VERSION + "/networks/" + NETWORK_UUID + "/run-and-save?contingencyListName=" + CONTINGENCY_LIST_NAME) - .exchange() - .expectStatus().isOk() - .expectHeader().contentType(MediaType.APPLICATION_JSON) - .expectBody(UUID.class) - .isEqualTo(RESULT_UUID); + public void deleteResultsTest() throws Exception { + MvcResult mvcResult; + String resultAsString; + + mvcResult = mockMvc.perform(post("/" + VERSION + "/networks/" + NETWORK_UUID + "/run-and-save?contingencyListName=" + CONTINGENCY_LIST_NAME)) + .andExpectAll( + status().isOk(), + content().contentType(MediaType.APPLICATION_JSON) + ).andReturn(); + + resultAsString = mvcResult.getResponse().getContentAsString(); + UUID resultUuid = mapper.readValue(resultAsString, UUID.class); + assertEquals(RESULT_UUID, resultUuid); output.receive(TIMEOUT, "sa.result"); - webTestClient.delete() - .uri("/" + VERSION + "/results") - .exchange() - .expectStatus().isOk(); + mockMvc.perform(delete("/" + VERSION + "/results")) + .andExpect(status().isOk()); assertResultNotFound(RESULT_UUID); } @Test - public void mergingViewTest() { - webTestClient.post() - .uri("/" + VERSION + "/networks/" + NETWORK_FOR_MERGING_VIEW_UUID + "/run?contingencyListName=" + CONTINGENCY_LIST_NAME + "&networkUuid=" + OTHER_NETWORK_FOR_MERGING_VIEW_UUID) - .exchange() - .expectStatus().isOk() - .expectHeader().contentType(MediaType.APPLICATION_JSON) - .expectBody(SecurityAnalysisResult.class) - .value(new MatcherJson<>(mapper, RESULT)); + public void mergingViewTest() throws Exception { + MvcResult mvcResult; + String resultAsString; + + mvcResult = mockMvc.perform(post("/" + VERSION + "/networks/" + NETWORK_FOR_MERGING_VIEW_UUID + "/run?contingencyListName=" + CONTINGENCY_LIST_NAME + "&networkUuid=" + OTHER_NETWORK_FOR_MERGING_VIEW_UUID)) + .andExpectAll( + status().isOk(), + content().contentType(MediaType.APPLICATION_JSON) + ).andReturn(); + + resultAsString = mvcResult.getResponse().getContentAsString(); + SecurityAnalysisResult securityAnalysisResult = mapper.readValue(resultAsString, SecurityAnalysisResult.class); + assertThat(RESULT, new MatcherJson<>(mapper, securityAnalysisResult)); } @Test - public void testStatus() { - webTestClient.get() - .uri("/" + VERSION + "/results/" + RESULT_UUID + "/status") - .exchange() - .expectStatus().isOk() - .expectBody(SecurityAnalysisStatus.class) - .isEqualTo(null); - - webTestClient.put() - .uri("/" + VERSION + "/results/invalidate-status?resultUuid=" + RESULT_UUID) - .exchange() - .expectStatus().isOk(); - - webTestClient.get() - .uri("/" + VERSION + "/results/" + RESULT_UUID + "/status") - .exchange() - .expectStatus().isOk() - .expectBody(SecurityAnalysisStatus.class) - .isEqualTo(SecurityAnalysisStatus.NOT_DONE); - - webTestClient.post() - .uri("/" + VERSION + "/networks/" + NETWORK_UUID + "/run-and-save?contingencyListName=" + CONTINGENCY_LIST_NAME - + "&receiver=me&variantId=" + VARIANT_2_ID + "&provider=OpenLoadFlow") - .exchange() - .expectStatus().isOk() - .expectHeader().contentType(MediaType.APPLICATION_JSON) - .expectBody(UUID.class) - .isEqualTo(RESULT_UUID); + public void testStatus() throws Exception { + MvcResult mvcResult; + String resultAsString; + + // getting status when result does not exist + mockMvc.perform(get("/" + VERSION + "/results/" + RESULT_UUID + "/status")) + .andExpectAll( + status().isOk(), + content().string("") + ); + + // invalidating unexisting result + mockMvc.perform(put("/" + VERSION + "/results/invalidate-status?resultUuid=" + RESULT_UUID)) + .andExpect(status().isOk()); + + // checking status is updated anyway + mvcResult = mockMvc.perform(get("/" + VERSION + "/results/" + RESULT_UUID + "/status")) + .andExpect(status().isOk()).andReturn(); + + resultAsString = mvcResult.getResponse().getContentAsString(); + SecurityAnalysisStatus securityAnalysisStatus = mapper.readValue(resultAsString, SecurityAnalysisStatus.class); + assertEquals(SecurityAnalysisStatus.NOT_DONE, securityAnalysisStatus); + + // running computation to create result + mvcResult = mockMvc.perform(post("/" + VERSION + "/networks/" + NETWORK_UUID + "/run-and-save?contingencyListName=" + CONTINGENCY_LIST_NAME + + "&receiver=me&variantId=" + VARIANT_2_ID + "&provider=OpenLoadFlow")) + .andExpectAll( + status().isOk(), + content().contentType(MediaType.APPLICATION_JSON) + ).andReturn(); + + resultAsString = mvcResult.getResponse().getContentAsString(); + UUID resultUuid = mapper.readValue(resultAsString, UUID.class); + assertEquals(RESULT_UUID, resultUuid); Message resultMessage = output.receive(TIMEOUT, "sa.result"); assertEquals(RESULT_UUID.toString(), resultMessage.getHeaders().get("resultUuid")); assertEquals("me", resultMessage.getHeaders().get("receiver")); - webTestClient.get() - .uri("/" + VERSION + "/results/" + RESULT_UUID + "/status") - .exchange() - .expectStatus().isOk() - .expectBody(SecurityAnalysisStatus.class) - .isEqualTo(SecurityAnalysisStatus.CONVERGED); - - webTestClient.put() - .uri("/" + VERSION + "/results/invalidate-status?resultUuid=" + RESULT_UUID) - .exchange() - .expectStatus().isOk(); - - webTestClient.get() - .uri("/" + VERSION + "/results/" + RESULT_UUID + "/status") - .exchange() - .expectStatus().isOk() - .expectBody(SecurityAnalysisStatus.class) - .isEqualTo(SecurityAnalysisStatus.NOT_DONE); + // getting status of this result + mvcResult = mockMvc.perform(get("/" + VERSION + "/results/" + RESULT_UUID + "/status")) + .andExpect(status().isOk()).andReturn(); + + resultAsString = mvcResult.getResponse().getContentAsString(); + securityAnalysisStatus = mapper.readValue(resultAsString, SecurityAnalysisStatus.class); + assertEquals(SecurityAnalysisStatus.CONVERGED, securityAnalysisStatus); + + // invalidating existing result + mockMvc.perform(put("/" + VERSION + "/results/invalidate-status?resultUuid=" + RESULT_UUID)) + .andExpect(status().isOk()); + + // checking invalidated status + mvcResult = mockMvc.perform(get("/" + VERSION + "/results/" + RESULT_UUID + "/status")) + .andExpect(status().isOk()).andReturn(); + + resultAsString = mvcResult.getResponse().getContentAsString(); + securityAnalysisStatus = mapper.readValue(resultAsString, SecurityAnalysisStatus.class); + assertEquals(SecurityAnalysisStatus.NOT_DONE, securityAnalysisStatus); } @Test - public void stopTest() { - webTestClient.post() - .uri("/" + VERSION + "/networks/" + NETWORK_STOP_UUID + "/run-and-save?contingencyListName=" + CONTINGENCY_LIST_NAME - + "&receiver=me&variantId=" + VARIANT_2_ID) - .exchange() - .expectStatus().isOk() - .expectHeader().contentType(MediaType.APPLICATION_JSON) - .expectBody(UUID.class) - .isEqualTo(RESULT_UUID); - - webTestClient.put() - .uri("/" + VERSION + "/results/" + RESULT_UUID + "/stop" - + "?receiver=me") - .exchange() - .expectStatus().isOk(); + public void stopTest() throws Exception { + countDownLatch = new CountDownLatch(1); + + new Thread(() -> { + try { + MvcResult mvcResult; + String resultAsString; + mvcResult = mockMvc.perform(post("/" + VERSION + "/networks/" + NETWORK_STOP_UUID + "/run-and-save?contingencyListName=" + CONTINGENCY_LIST_NAME + + "&receiver=me&variantId=" + VARIANT_TO_STOP_ID)) + .andExpectAll( + status().isOk(), + content().contentType(MediaType.APPLICATION_JSON) + ).andReturn(); + + resultAsString = mvcResult.getResponse().getContentAsString(); + UUID resultUuid = mapper.readValue(resultAsString, UUID.class); + assertEquals(RESULT_UUID, resultUuid); + } catch (Exception e) { + throw new RuntimeException(e.getMessage()); + } + }).start(); + + // wait for security analysis to actually run before trying to stop it + countDownLatch.await(); + + mockMvc.perform(put("/" + VERSION + "/results/" + RESULT_UUID + "/stop" + + "?receiver=me")) + .andExpect(status().isOk()); Message message = output.receive(TIMEOUT * 3, "sa.stopped"); assertEquals(RESULT_UUID.toString(), message.getHeaders().get("resultUuid")); @@ -411,15 +468,23 @@ public void stopTest() { } @Test - public void runTestWithError() { - webTestClient.post() - .uri("/" + VERSION + "/networks/" + NETWORK_UUID + "/run-and-save?contingencyListName=" + CONTINGENCY_LIST_ERROR_NAME - + "&receiver=me&variantId=" + VARIANT_1_ID) - .exchange() - .expectStatus().isOk() // Because fully asynchronous (just publish a message) - .expectHeader().contentType(MediaType.APPLICATION_JSON) - .expectBody(UUID.class) - .isEqualTo(RESULT_UUID); + public void runTestWithError() throws Exception { + MvcResult mvcResult; + String resultAsString; + + given(actionsService.getContingencyList(CONTINGENCY_LIST_ERROR_NAME, NETWORK_UUID, VARIANT_1_ID)) + .willThrow(new RuntimeException(ERROR_MESSAGE)); + + mvcResult = mockMvc.perform(post("/" + VERSION + "/networks/" + NETWORK_UUID + "/run-and-save?contingencyListName=" + CONTINGENCY_LIST_ERROR_NAME + + "&receiver=me&variantId=" + VARIANT_1_ID)) + .andExpectAll( + status().isOk(), + content().contentType(MediaType.APPLICATION_JSON) + ).andReturn(); + + resultAsString = mvcResult.getResponse().getContentAsString(); + UUID resultUuid = mapper.readValue(resultAsString, UUID.class); + assertEquals(RESULT_UUID, resultUuid); // Message stopped has been sent Message cancelMessage = output.receive(TIMEOUT, "sa.failed"); @@ -432,52 +497,55 @@ public void runTestWithError() { } @Test - public void runWithReportTest() { - webTestClient.post() - .uri("/" + VERSION + "/networks/" + NETWORK_UUID + "/run?contingencyListName=" + CONTINGENCY_LIST_NAME + "&provider=testProvider" + "&reportUuid=" + REPORT_UUID + "&reporterId=" + UUID.randomUUID()) - .exchange() - .expectStatus().isOk() - .expectHeader().contentType(MediaType.APPLICATION_JSON) - .expectBody(SecurityAnalysisResult.class) - .value(new MatcherJson<>(mapper, RESULT)); + public void runWithReportTest() throws Exception { + MvcResult mvcResult; + String resultAsString; + + mvcResult = mockMvc.perform(post("/" + VERSION + "/networks/" + NETWORK_UUID + "/run?contingencyListName=" + CONTINGENCY_LIST_NAME + "&provider=testProvider" + "&reportUuid=" + REPORT_UUID + "&reporterId=" + UUID.randomUUID()).contentType(MediaType.APPLICATION_JSON)) + .andExpectAll( + status().isOk(), + content().contentType(MediaType.APPLICATION_JSON)) + .andReturn(); + + resultAsString = mvcResult.getResponse().getContentAsString(); + SecurityAnalysisResult securityAnalysisResult = mapper.readValue(resultAsString, SecurityAnalysisResult.class); + assertThat(RESULT, new MatcherJson<>(mapper, securityAnalysisResult)); } @Test - public void getProvidersTest() { - webTestClient.get() - .uri("/" + VERSION + "/providers") - .exchange() - .expectStatus().isOk() - .expectHeader().contentType(MediaType.APPLICATION_JSON) - .expectBody(List.class) - .isEqualTo(List.of("DynaFlow", "OpenLoadFlow", "Hades2")); + public void getProvidersTest() throws Exception { + MvcResult mvcResult; + String resultAsString; + + mvcResult = mockMvc.perform(get("/" + VERSION + "/providers")) + .andExpectAll( + status().isOk(), + content().contentType(MediaType.APPLICATION_JSON) + ).andReturn(); + + resultAsString = mvcResult.getResponse().getContentAsString(); + List providers = mapper.readValue(resultAsString, new TypeReference>() { }); + assertEquals(List.of("DynaFlow", "OpenLoadFlow", "Hades2"), providers); } @Test - public void getDefaultProviderTest() { - webTestClient.get() - .uri("/" + VERSION + "/default-provider") - .exchange() - .expectStatus().isOk() - .expectHeader().contentType(new MediaType(MediaType.TEXT_PLAIN, StandardCharsets.UTF_8)) - .expectBody(String.class) - .isEqualTo("OpenLoadFlow"); + public void getDefaultProviderTest() throws Exception { + mockMvc.perform(get("/" + VERSION + "/default-provider")) + .andExpectAll( + status().isOk(), + content().contentType(new MediaType(MediaType.TEXT_PLAIN, StandardCharsets.UTF_8)), + content().string("OpenLoadFlow") + ); } - private void assertResultNotFound(UUID resultUuid) { - webTestClient.get() - .uri("/" + VERSION + "/results/" + resultUuid + "/n-result") - .exchange() - .expectStatus().isNotFound(); - - webTestClient.get() - .uri("/" + VERSION + "/results/" + resultUuid + "/nmk-contingencies-result") - .exchange() - .expectStatus().isNotFound(); - - webTestClient.get() - .uri("/" + VERSION + "/results/" + resultUuid + "/nmk-constraints-result") - .exchange() - .expectStatus().isNotFound(); + private void assertResultNotFound(UUID resultUuid) throws Exception { + mockMvc.perform(get("/" + VERSION + "/results/" + resultUuid + "/n-result")) + .andExpect(status().isNotFound()); + + mockMvc.perform(get("/" + VERSION + "/results/" + resultUuid + "/nmk-contingencies-result")) + .andExpect(status().isNotFound()); + + mockMvc.perform(get("/" + VERSION + "/results/" + resultUuid + "/nmk-constraints-result")) + .andExpect(status().isNotFound()); } } diff --git a/src/test/java/org/gridsuite/securityanalysis/server/SecurityAnalysisProviderMock.java b/src/test/java/org/gridsuite/securityanalysis/server/SecurityAnalysisProviderMock.java index 8233fc4b..add48213 100644 --- a/src/test/java/org/gridsuite/securityanalysis/server/SecurityAnalysisProviderMock.java +++ b/src/test/java/org/gridsuite/securityanalysis/server/SecurityAnalysisProviderMock.java @@ -8,6 +8,8 @@ import java.util.List; import java.util.concurrent.CompletableFuture; +import java.util.concurrent.CountDownLatch; +import java.util.concurrent.TimeUnit; import java.util.stream.Collectors; import com.powsybl.commons.reporter.Reporter; @@ -81,40 +83,29 @@ public class SecurityAnalysisProviderMock implements SecurityAnalysisProvider { CONTINGENCIES_VARIANT.stream().map(contingency -> new PostContingencyResult(contingency, PostContingencyComputationStatus.CONVERGED, List.of(LIMIT_VIOLATION_4))) .collect(Collectors.toList())); - static final List RESULT_CONTINGENCIES = CONTINGENCIES.stream().map(c -> - new ContingencyToSubjectLimitViolationDTO( - c.getId(), - LoadFlowResult.ComponentResult.Status.CONVERGED.name(), - c.getElements().stream().map(e -> new ContingencyElementDTO(e.getId(), e.getType())).collect(Collectors.toList()), - List.of(new SubjectLimitViolationFromContingencyDTO( + static final List RESULT_CONTINGENCIES = CONTINGENCIES.stream().map(c -> + new ContingencyResultDTO( + new ContingencyDTO( + c.getId(), + LoadFlowResult.ComponentResult.Status.CONVERGED.name(), + c.getElements().stream().map(e -> new ContingencyElementDTO(e.getId(), e.getType())).collect(Collectors.toList()) + ), + List.of(new SubjectLimitViolationDTO( LIMIT_VIOLATION_2.getSubjectId(), - LIMIT_VIOLATION_2.getLimitType(), - LIMIT_VIOLATION_2.getLimitName(), - LIMIT_VIOLATION_2.getSide(), - LIMIT_VIOLATION_2.getAcceptableDuration(), - LIMIT_VIOLATION_2.getLimit(), - LIMIT_VIOLATION_2.getLimitReduction(), - LIMIT_VIOLATION_2.getValue()) + toLimitViolationDTO(LIMIT_VIOLATION_2) ) - )).collect(Collectors.toList() + ))).collect(Collectors.toList() ); - static final List RESULT_CONSTRAINTS = List.of( - new SubjectLimitViolationToContingencyDTO(LIMIT_VIOLATION_1.getSubjectId(), List.of()), - new SubjectLimitViolationToContingencyDTO( + static final List RESULT_CONSTRAINTS = List.of( + new SubjectLimitViolationResultDTO(LIMIT_VIOLATION_1.getSubjectId(), List.of()), + new SubjectLimitViolationResultDTO( LIMIT_VIOLATION_2.getSubjectId(), - CONTINGENCIES.stream().map(c -> new ContingencyFromSubjectLimitViolationDTO( - c.getId(), - LoadFlowResult.ComponentResult.Status.CONVERGED.name(), - LIMIT_VIOLATION_2.getLimitType(), - LIMIT_VIOLATION_2.getLimitName(), - LIMIT_VIOLATION_2.getSide(), - LIMIT_VIOLATION_2.getAcceptableDuration(), - LIMIT_VIOLATION_2.getLimit(), - LIMIT_VIOLATION_2.getLimitReduction(), - LIMIT_VIOLATION_2.getValue(), - c.getElements().stream().map(e -> new ContingencyElementDTO(e.getId(), e.getType())).collect(Collectors.toList()))) - .collect(Collectors.toList()) + CONTINGENCIES.stream().map(c -> new ContingencyLimitViolationDTO( + new ContingencyDTO(c.getId(), LoadFlowResult.ComponentResult.Status.CONVERGED.name(), c.getElements().stream().map(e -> new ContingencyElementDTO(e.getId(), e.getType())).collect(Collectors.toList())), + toLimitViolationDTO(LIMIT_VIOLATION_2) + ) + ).collect(Collectors.toList()) )); static final SecurityAnalysisReport REPORT = new SecurityAnalysisReport(RESULT); @@ -123,6 +114,9 @@ public class SecurityAnalysisProviderMock implements SecurityAnalysisProvider { static final String VARIANT_1_ID = "variant_1"; static final String VARIANT_2_ID = "variant_2"; static final String VARIANT_3_ID = "variant_3"; + static final String VARIANT_TO_STOP_ID = "variant_to_stop"; + + static CountDownLatch countDownLatch; public CompletableFuture run(Network network, String workingVariantId, @@ -137,10 +131,17 @@ public CompletableFuture run(Network network, List monitors, Reporter reporter) { LOGGER.info("Run security analysis mock"); - if (workingVariantId.equals(VARIANT_3_ID)) { - return CompletableFuture.completedFuture(REPORT_VARIANT); + switch (workingVariantId) { + case VARIANT_3_ID: + return CompletableFuture.completedFuture(REPORT_VARIANT); + case VARIANT_TO_STOP_ID: + countDownLatch.countDown(); + // creating a long completable future which is here to be canceled + return new CompletableFuture().completeOnTimeout(REPORT, 3, TimeUnit.SECONDS); + default: + return CompletableFuture.completedFuture(REPORT); } - return CompletableFuture.completedFuture(REPORT); + } @Override @@ -152,4 +153,20 @@ public String getName() { public String getVersion() { return "1"; } + + private static LimitViolationDTO toLimitViolationDTO(LimitViolation limitViolation) { + Double computedLoading = limitViolation.getLimitType().equals(LimitViolationType.CURRENT) + ? (100 * limitViolation.getValue()) / (limitViolation.getLimit() * limitViolation.getLimitReduction()) + : null; + return new LimitViolationDTO( + limitViolation.getLimitType(), + limitViolation.getLimitName(), + limitViolation.getSide(), + limitViolation.getAcceptableDuration(), + limitViolation.getLimit(), + limitViolation.getLimitReduction(), + limitViolation.getValue(), + computedLoading + ); + } } diff --git a/src/test/java/org/gridsuite/securityanalysis/server/SupervisionControllerTest.java b/src/test/java/org/gridsuite/securityanalysis/server/SupervisionControllerTest.java index 746e6684..8f9da956 100644 --- a/src/test/java/org/gridsuite/securityanalysis/server/SupervisionControllerTest.java +++ b/src/test/java/org/gridsuite/securityanalysis/server/SupervisionControllerTest.java @@ -9,35 +9,32 @@ import org.junit.Test; import org.junit.runner.RunWith; import org.springframework.beans.factory.annotation.Autowired; -import org.springframework.boot.test.autoconfigure.web.reactive.AutoConfigureWebTestClient; +import org.springframework.boot.test.autoconfigure.web.servlet.AutoConfigureMockMvc; import org.springframework.boot.test.context.SpringBootTest; import org.springframework.test.context.junit4.SpringRunner; -import org.springframework.test.web.reactive.server.EntityExchangeResult; -import org.springframework.test.web.reactive.server.WebTestClient; -import static org.junit.Assert.assertEquals; +import org.springframework.test.web.servlet.MockMvc; + +import static org.springframework.test.web.servlet.request.MockMvcRequestBuilders.*; +import static org.springframework.test.web.servlet.result.MockMvcResultMatchers.*; /** * @author Hugo Marcellin */ @RunWith(SpringRunner.class) -@AutoConfigureWebTestClient +@AutoConfigureMockMvc @SpringBootTest public class SupervisionControllerTest { @Autowired - private WebTestClient webTestClient; + private MockMvc mockMvc; @Test - public void testResultCount() { + public void testResultCount() throws Exception { //get the result timeline uuid of the calculation - EntityExchangeResult entityExchangeResult = webTestClient.get() - .uri("/v1/supervision/results-count") - .exchange() - .expectStatus().isOk() - .expectBody(Integer.class).returnResult(); - - int resultCount = entityExchangeResult.getResponseBody(); - assertEquals(0, resultCount); - + mockMvc.perform(get("/v1/supervision/results-count")) + .andExpectAll( + status().isOk(), + content().string("0") + ); } } diff --git a/src/test/java/org/gridsuite/securityanalysis/server/service/ActionsServiceTest.java b/src/test/java/org/gridsuite/securityanalysis/server/service/ActionsServiceTest.java index d56026eb..b3078ae6 100644 --- a/src/test/java/org/gridsuite/securityanalysis/server/service/ActionsServiceTest.java +++ b/src/test/java/org/gridsuite/securityanalysis/server/service/ActionsServiceTest.java @@ -14,18 +14,15 @@ import okhttp3.mockwebserver.MockResponse; import okhttp3.mockwebserver.MockWebServer; import okhttp3.mockwebserver.RecordedRequest; -import org.gridsuite.securityanalysis.server.WebFluxConfig; +import org.gridsuite.securityanalysis.server.util.ContextConfigurationWithTestChannel; import org.junit.After; import org.junit.Before; import org.junit.Test; import org.junit.runner.RunWith; +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.boot.test.context.SpringBootTest; import org.springframework.http.HttpStatus; -import org.springframework.http.MediaType; -import org.springframework.http.codec.json.Jackson2JsonDecoder; -import org.springframework.http.codec.json.Jackson2JsonEncoder; import org.springframework.test.context.junit4.SpringRunner; -import org.springframework.web.reactive.function.client.ExchangeStrategies; -import org.springframework.web.reactive.function.client.WebClient; import java.io.IOException; import java.util.List; @@ -40,7 +37,9 @@ * @author Geoffroy Jamgotchian * @author Slimane Amar */ +@SpringBootTest @RunWith(SpringRunner.class) +@ContextConfigurationWithTestChannel public class ActionsServiceTest { private static final int DATA_BUFFER_LIMIT = 256 * 1024; // AbstractJackson2Decoder.maxInMemorySize @@ -57,27 +56,18 @@ public class ActionsServiceTest { private static final Contingency CONTINGENCY_VARIANT = new Contingency("c2", new BranchContingency("b2")); - private final ObjectMapper objectMapper = WebFluxConfig.createObjectMapper(); - - private WebClient.Builder webClientBuilder; + @Autowired + private ObjectMapper objectMapper; private MockWebServer server; + @Autowired private ActionsService actionsService; @Before public void setUp() throws IOException { - webClientBuilder = WebClient.builder(); - ExchangeStrategies strategies = ExchangeStrategies - .builder() - .codecs(clientDefaultCodecsConfigurer -> { - clientDefaultCodecsConfigurer.defaultCodecs().jackson2JsonEncoder(new Jackson2JsonEncoder(objectMapper, MediaType.APPLICATION_JSON)); - clientDefaultCodecsConfigurer.defaultCodecs().jackson2JsonDecoder(new Jackson2JsonDecoder(objectMapper, MediaType.APPLICATION_JSON)); - - }).build(); - webClientBuilder.exchangeStrategies(strategies); - - actionsService = new ActionsService(webClientBuilder, initMockWebServer()); + String mockServerUri = initMockWebServer(); + actionsService.setActionServiceBaseUri(mockServerUri); } @After @@ -134,18 +124,18 @@ private List createVeryLargeList() { @Test public void test() { - List list = actionsService.getContingencyList(LIST_NAME, UUID.fromString(NETWORK_UUID), null).collectList().block(); + List list = actionsService.getContingencyList(LIST_NAME, UUID.fromString(NETWORK_UUID), null); assertEquals(List.of(CONTINGENCY), list); - list = actionsService.getContingencyList(LIST_NAME, UUID.fromString(NETWORK_UUID), VARIANT_ID).collectList().block(); + list = actionsService.getContingencyList(LIST_NAME, UUID.fromString(NETWORK_UUID), VARIANT_ID); assertEquals(List.of(CONTINGENCY_VARIANT), list); } @Test public void testVeryLargeList() { // DataBufferLimitException should not be thrown with this message : "Exceeded limit on max bytes to buffer : DATA_BUFFER_LIMIT" - List list = actionsService.getContingencyList(VERY_LARGE_LIST_NAME, UUID.fromString(NETWORK_UUID), null).collectList().block(); + List list = actionsService.getContingencyList(VERY_LARGE_LIST_NAME, UUID.fromString(NETWORK_UUID), null); assertEquals(createVeryLargeList(), list); - list = actionsService.getContingencyList(VERY_LARGE_LIST_NAME, UUID.fromString(NETWORK_UUID), VARIANT_ID).collectList().block(); + list = actionsService.getContingencyList(VERY_LARGE_LIST_NAME, UUID.fromString(NETWORK_UUID), VARIANT_ID); assertEquals(createVeryLargeList(), list); } } diff --git a/src/test/java/org/gridsuite/securityanalysis/server/service/ReportServiceTest.java b/src/test/java/org/gridsuite/securityanalysis/server/service/ReportServiceTest.java index 18f7c03e..7fceaa85 100644 --- a/src/test/java/org/gridsuite/securityanalysis/server/service/ReportServiceTest.java +++ b/src/test/java/org/gridsuite/securityanalysis/server/service/ReportServiceTest.java @@ -6,7 +6,6 @@ */ package org.gridsuite.securityanalysis.server.service; -import com.fasterxml.jackson.databind.ObjectMapper; import com.powsybl.commons.reporter.Reporter; import com.powsybl.commons.reporter.ReporterModel; import okhttp3.HttpUrl; @@ -14,57 +13,43 @@ import okhttp3.mockwebserver.MockResponse; import okhttp3.mockwebserver.MockWebServer; import okhttp3.mockwebserver.RecordedRequest; -import org.gridsuite.securityanalysis.server.WebFluxConfig; +import org.gridsuite.securityanalysis.server.util.ContextConfigurationWithTestChannel; import org.junit.After; import org.junit.Before; import org.junit.Test; import org.junit.runner.RunWith; +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.boot.test.context.SpringBootTest; import org.springframework.http.HttpStatus; -import org.springframework.http.MediaType; -import org.springframework.http.codec.json.Jackson2JsonDecoder; -import org.springframework.http.codec.json.Jackson2JsonEncoder; import org.springframework.test.context.junit4.SpringRunner; -import org.springframework.web.reactive.function.client.ExchangeStrategies; -import org.springframework.web.reactive.function.client.WebClient; import java.io.IOException; import java.util.Objects; import java.util.UUID; import static org.junit.Assert.assertEquals; -import static org.junit.Assert.assertNull; /** * @author Geoffroy Jamgotchian */ +@SpringBootTest @RunWith(SpringRunner.class) +@ContextConfigurationWithTestChannel public class ReportServiceTest { private static final UUID REPORT_UUID = UUID.fromString("7928181c-7977-4592-ba19-88027e4254e4"); private static final String REPORT_JSON = "{\"version\":\"1.0\",\"reportTree\":{\"taskKey\":\"test\"},\"dics\":{\"default\":{\"test\":\"a test\"}}}"; - private final ObjectMapper objectMapper = WebFluxConfig.createObjectMapper(); - - private WebClient.Builder webClientBuilder; - private MockWebServer server; + @Autowired private ReportService reportService; @Before public void setUp() throws IOException { - webClientBuilder = WebClient.builder(); - ExchangeStrategies strategies = ExchangeStrategies - .builder() - .codecs(clientDefaultCodecsConfigurer -> { - clientDefaultCodecsConfigurer.defaultCodecs().jackson2JsonEncoder(new Jackson2JsonEncoder(objectMapper, MediaType.APPLICATION_JSON)); - clientDefaultCodecsConfigurer.defaultCodecs().jackson2JsonDecoder(new Jackson2JsonDecoder(objectMapper, MediaType.APPLICATION_JSON)); - - }).build(); - webClientBuilder.exchangeStrategies(strategies); - - reportService = new ReportService(webClientBuilder, initMockWebServer()); + String mockServerUri = initMockWebServer(); + reportService.setReportServiceBaseUri(mockServerUri); } @After @@ -104,6 +89,6 @@ public MockResponse dispatch(RecordedRequest request) { @Test public void test() { Reporter reporter = new ReporterModel("test", "a test"); - assertNull(reportService.sendReport(REPORT_UUID, reporter).block()); + reportService.sendReport(REPORT_UUID, reporter); } } diff --git a/src/test/java/org/gridsuite/securityanalysis/server/util/ContextConfigurationWithTestChannel.java b/src/test/java/org/gridsuite/securityanalysis/server/util/ContextConfigurationWithTestChannel.java new file mode 100644 index 00000000..23a926c6 --- /dev/null +++ b/src/test/java/org/gridsuite/securityanalysis/server/util/ContextConfigurationWithTestChannel.java @@ -0,0 +1,26 @@ +/* + * Copyright (c) 2023, RTE (http://www.rte-france.com) + * This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. + */ + +package org.gridsuite.securityanalysis.server.util; + +import org.gridsuite.securityanalysis.server.SecurityAnalysisApplication; +import org.springframework.cloud.stream.binder.test.TestChannelBinderConfiguration; +import org.springframework.test.context.ContextConfiguration; + +import java.lang.annotation.ElementType; +import java.lang.annotation.Retention; +import java.lang.annotation.RetentionPolicy; +import java.lang.annotation.Target; + +/** + * @author Slimane Amar + */ +@Target(ElementType.TYPE) +@Retention(RetentionPolicy.RUNTIME) +@ContextConfiguration(classes = {SecurityAnalysisApplication.class, TestChannelBinderConfiguration.class}) +public @interface ContextConfigurationWithTestChannel { +}