Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
9 changes: 9 additions & 0 deletions pom.xml
Original file line number Diff line number Diff line change
Expand Up @@ -68,6 +68,7 @@
<configuration>
<classpathDependencyExcludes>
<classpathDependencyExclude>com.powsybl:powsybl-config-classic</classpathDependencyExclude>
<classpathDependencyExclude>com.powsybl:powsybl-iidm-impl</classpathDependencyExclude>
</classpathDependencyExcludes>
</configuration>
</plugin>
Expand Down Expand Up @@ -162,6 +163,10 @@
<groupId>com.powsybl</groupId>
<artifactId>powsybl-iidm-api</artifactId>
</dependency>
<dependency>
<groupId>com.powsybl</groupId>
<artifactId>powsybl-iidm-impl</artifactId>
</dependency>
<dependency>
<groupId>com.powsybl</groupId>
<artifactId>powsybl-iidm-modification</artifactId>
Expand Down Expand Up @@ -199,6 +204,10 @@
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-actuator</artifactId>
</dependency>
<dependency>
<groupId>com.powsybl</groupId>
<artifactId>powsybl-case-datasource-client</artifactId>
</dependency>

<!-- elasticsearch -->
<dependency>
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,48 @@
/**
* Copyright (c) 2026, 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.modification.server;

import io.swagger.v3.oas.annotations.Operation;
import io.swagger.v3.oas.annotations.Parameter;
import io.swagger.v3.oas.annotations.responses.ApiResponse;
import io.swagger.v3.oas.annotations.tags.Tag;
import org.gridsuite.modification.server.service.NetworkModificationOnCaseService;
import org.springframework.http.MediaType;
import org.springframework.http.ResponseEntity;
import org.springframework.web.bind.annotation.PathVariable;
import org.springframework.web.bind.annotation.PostMapping;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RequestParam;
import org.springframework.web.bind.annotation.RestController;

import java.util.List;
import java.util.UUID;

/**
* @author Franck Lecuyer <franck.lecuyer at rte-france.com>
*/
@RestController
@RequestMapping(value = "/" + NetworkModificationApi.API_VERSION + "/cases")
@Tag(name = "network-modification-server on case")
public class NetworkModificationOnCaseController {

private final NetworkModificationOnCaseService networkModificationOnCaseService;

public NetworkModificationOnCaseController(NetworkModificationOnCaseService networkModificationOnCaseService) {
this.networkModificationOnCaseService = networkModificationOnCaseService;
}

@PostMapping(value = "/{caseUuid}/network-composite-modifications", produces = MediaType.APPLICATION_JSON_VALUE)
@Operation(summary = "Apply a list of composite modifications on a case")
@ApiResponse(responseCode = "200", description = "Composite modifications applied on case")
public ResponseEntity<Void> applyNetworkCompositeModificationsOnCase(@Parameter(description = "Case UUID") @PathVariable("caseUuid") UUID caseUuid,
@Parameter(description = "Execution UUID") @RequestParam(name = "executionUuid", required = false) UUID executionUuid,
@Parameter(description = "Composite modifications uuids list") @RequestParam("uuids") List<UUID> compositeModificationUuids) {
networkModificationOnCaseService.applyNetworkCompositeModificationsOnCase(caseUuid, executionUuid, compositeModificationUuids);
return ResponseEntity.ok().build();
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,38 @@
/**
* Copyright (c) 2026, 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.modification.server.service;

import lombok.Getter;

import java.util.UUID;

/**
* @author Franck Lecuyer <franck.lecuyer at rte-france.com>
*/
@Getter
public class CaseResultInfos {
private final UUID caseResultUuid;

private final UUID executionUuid;

private final UUID reportUuid;

private final UUID resultUuid;

private final String stepType;

private final String status;

public CaseResultInfos(UUID caseResultUuid, UUID executionUuid, UUID reportUuid, UUID resultUuid, String stepType, String status) {
this.caseResultUuid = caseResultUuid;
this.executionUuid = executionUuid;
this.reportUuid = reportUuid;
this.resultUuid = resultUuid;
this.stepType = stepType;
this.status = status;
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,52 @@
/**
* Copyright (c) 2026, 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.modification.server.service;

import com.powsybl.cases.datasource.CaseDataSourceClient;
import com.powsybl.commons.PowsyblException;
import com.powsybl.commons.report.ReportNode;
import com.powsybl.computation.local.LocalComputationManager;
import com.powsybl.iidm.network.Importer;
import com.powsybl.iidm.network.Network;
import com.powsybl.iidm.network.NetworkFactory;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.boot.web.client.RestTemplateBuilder;
import org.springframework.stereotype.Service;
import org.springframework.web.client.RestTemplate;

import java.util.Properties;
import java.util.UUID;

/**
* @author Franck Lecuyer <franck.lecuyer at rte-france.com>
*/
@Service
public class NetworkConversionService {
private static final Logger LOGGER = LoggerFactory.getLogger(NetworkConversionService.class);

private final RestTemplate caseServerRest;

public NetworkConversionService(@Value("${powsybl.services.case-server.base-uri:http://case-server/}") String caseServerBaseUri,
RestTemplateBuilder restTemplateBuilder) {
this.caseServerRest = restTemplateBuilder.rootUri(caseServerBaseUri).build();
}

public Network createNetwork(UUID caseUuid, ReportNode reporter) {
LOGGER.info("Creating network");

CaseDataSourceClient dataSource = new CaseDataSourceClient(caseServerRest, caseUuid);

Importer importer = Importer.find(dataSource, LocalComputationManager.getDefault());
if (importer == null) {
throw new PowsyblException("No importer found");
} else {
return importer.importData(dataSource, NetworkFactory.findDefault(), new Properties(), reporter);
}
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,168 @@
/*
Copyright (c) 2026, 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.modification.server.service;

import com.powsybl.commons.PowsyblException;
import com.powsybl.commons.datasource.MemDataSource;
import com.powsybl.commons.report.ReportNode;
import com.powsybl.iidm.network.Network;
import org.gridsuite.modification.NetworkModificationException;
import org.gridsuite.modification.dto.ModificationInfos;
import org.gridsuite.modification.modifications.AbstractModification;
import org.gridsuite.modification.server.dto.ReportMode;
import org.gridsuite.modification.server.repositories.NetworkModificationRepository;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.boot.web.client.RestTemplateBuilder;
import org.springframework.core.io.ByteArrayResource;
import org.springframework.core.io.Resource;
import org.springframework.http.HttpEntity;
import org.springframework.http.HttpHeaders;
import org.springframework.http.MediaType;
import org.springframework.messaging.support.MessageBuilder;
import org.springframework.stereotype.Service;
import org.springframework.util.LinkedMultiValueMap;
import org.springframework.util.MultiValueMap;
import org.springframework.web.client.RestTemplate;

import java.io.IOException;
import java.util.List;
import java.util.Set;
import java.util.UUID;

/**
* @author Franck Lecuyer <franck.lecuyer at rte-france.com>
*/
@Service
public class NetworkModificationOnCaseService {
private static final Logger LOGGER = LoggerFactory.getLogger(NetworkModificationOnCaseService.class);

private final NetworkModificationRepository networkModificationRepository;

private final NetworkConversionService networkConversionService;

private final FilterService filterService;

private final ReportService reportService;

private final NotificationService notificationService;

private final RestTemplate restTemplate;

private final String caseExportFormat = "XIIDM";

public NetworkModificationOnCaseService(NetworkModificationRepository networkModificationRepository,
NetworkConversionService networkConversionService,
FilterService filterService,
NotificationService notificationService,
ReportService reportService,
RestTemplateBuilder restTemplateBuilder,
@Value("${powsybl.services.case-server.base-uri:http://case-server}") String caseServerBaseUri) {
this.networkModificationRepository = networkModificationRepository;
this.networkConversionService = networkConversionService;
this.filterService = filterService;
this.notificationService = notificationService;
this.reportService = reportService;
this.restTemplate = restTemplateBuilder.rootUri(caseServerBaseUri).build();
}

private Network loadNetworkFromCase(UUID caseUuid, ReportNode reportNode) {
return networkConversionService.createNetwork(caseUuid, reportNode);
}

private List<ModificationInfos> getModificationsFromCompositeModifications(List<UUID> compositeModificationUuids) {
return networkModificationRepository.getCompositeModificationsInfos(compositeModificationUuids);
}

private void applyModifications(Network network, List<ModificationInfos> modificationsInfos, ReportNode reportNode, FilterService filterService) {
modificationsInfos.stream()
.filter(ModificationInfos::getActivated)
.forEach(modificationInfos -> {
try {
AbstractModification modification = modificationInfos.toModification();
modification.check(network);
modification.initApplicationContext(filterService, null);
modification.apply(network, reportNode);
} catch (Exception e) {
// For now, we just log the error, and we continue to apply the following modifications
handleException(modificationInfos.getErrorType(), e);
}
});
}

private UUID save(Resource resource) {
String uri = "/v1/cases";

MultiValueMap<String, Object> body = new LinkedMultiValueMap<>();
HttpHeaders headers = new HttpHeaders();
headers.setContentType(MediaType.MULTIPART_FORM_DATA);
body.add("file", resource);
HttpEntity<MultiValueMap<String, Object>> request = new HttpEntity<>(body, headers);

return restTemplate.postForObject(uri, request, UUID.class);
}

private UUID save(Network network) throws IOException {
MemDataSource memDataSource = new MemDataSource();
network.write(this.caseExportFormat, null, memDataSource);

Set<String> listNames = memDataSource.listNames(".*");
String caseFileName = "apply-modifications-output." + this.caseExportFormat.toLowerCase();
return save(new ByteArrayResource(memDataSource.getData(listNames.toArray()[0].toString())) {
@Override
public String getFilename() {
return caseFileName;
}
});
}

private void handleException(NetworkModificationException.Type typeIfError, Exception e) {
boolean isApplicationException = PowsyblException.class.isAssignableFrom(e.getClass());
if (!isApplicationException) {
LOGGER.error("{}", e.getMessage(), e);
} else {
LOGGER.error("{} : {}", typeIfError.name(), e.getMessage(), e);
}
}

public void applyNetworkCompositeModificationsOnCase(UUID caseUuid, UUID executionUuid, List<UUID> compositeModificationUuids) {
UUID resultCaseUuid = null;
UUID reportUuid = null;
String status = "COMPLETED";

try {
ReportNode rootReport = ReportNode.newRootReportNode()
.withAllResourceBundlesFromClasspath()
.withMessageTemplate("network.modification.server.caseUuid")
.withUntypedValue("caseUuid", caseUuid.toString())
.build();

LOGGER.info("Applying modifications on case {}", caseUuid);

// create network from case
Network network = loadNetworkFromCase(caseUuid, rootReport);

// get modifications from composite modifications
List<ModificationInfos> modifications = getModificationsFromCompositeModifications(compositeModificationUuids);

// apply modifications
applyModifications(network, modifications, rootReport, filterService);

// send report to report server
reportUuid = UUID.randomUUID();
reportService.sendReport(reportUuid, rootReport, ReportMode.APPEND);

// save network in case server
resultCaseUuid = save(network);
} catch (Exception e) {
status = "FAILED";
} finally {
notificationService.sendMessage(MessageBuilder.withPayload(new CaseResultInfos(resultCaseUuid, executionUuid, reportUuid, null, "APPLY_MODIFICATIONS", status)).build(), "publishCaseResult-out-0");
}
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -36,7 +36,7 @@ public class NotificationService {
@Autowired
private StreamBridge publisher;

private void sendMessage(Message<? extends Object> message, String bindingName) {
public void sendMessage(Message<? extends Object> message, String bindingName) {
OUTPUT_MESSAGE_LOGGER.debug("Sending message : {}", message);
publisher.send(bindingName, message);
}
Expand Down
2 changes: 2 additions & 0 deletions src/main/resources/application-local.yml
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,8 @@ powsybl:
services:
network-store-server:
base-uri: http://localhost:8080
case-server:
base-uri: http://localhost:5000

gridsuite:
services:
Expand Down
4 changes: 3 additions & 1 deletion src/main/resources/config/application.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -37,7 +37,9 @@ spring:
destination: ${powsybl-ws.rabbitmq.destination.prefix:}build.cancel
publishStoppedBuild-out-0:
destination: ${powsybl-ws.rabbitmq.destination.prefix:}build.stopped
output-bindings: publishBuild-out-0;publishResultBuild-out-0;publishCancelBuild-out-0;publishStoppedBuild-out-0
publishCaseResult-out-0:
destination: ${powsybl-ws.rabbitmq.destination.prefix:}modifications.case.result
output-bindings: publishBuild-out-0;publishResultBuild-out-0;publishCancelBuild-out-0;publishStoppedBuild-out-0;publishCaseResult-out-0
rabbit:
bindings:
consumeBuild-in-0:
Expand Down
Original file line number Diff line number Diff line change
@@ -1,2 +1,3 @@
network.modification.server.errorMessage = ${errorMessage}
network.modification.server.nodeUuid = ${nodeUuid}
network.modification.server.caseUuid = ${caseUuid}