diff --git a/pom.xml b/pom.xml index 0c187c2..3ed2b06 100644 --- a/pom.xml +++ b/pom.xml @@ -56,6 +56,10 @@ org.gridsuite.voltageinit.server gridsuite org.gridsuite:voltage-init-server + + 1.7.0 + 1.34.0 + 1.15.0 @@ -90,6 +94,24 @@ + + + com.powsybl + powsybl-ws-commons + ${powsybl-ws-commons.version} + + + + org.gridsuite + gridsuite-computation + ${gridsuite-computation.version} + + + + org.gridsuite + gridsuite-filter + ${gridsuite-filter.version} + com.squareup.okhttp3 diff --git a/src/main/java/org/gridsuite/voltageinit/server/PropertyServerNameProvider.java b/src/main/java/org/gridsuite/voltageinit/server/PropertyServerNameProvider.java new file mode 100644 index 0000000..5a40230 --- /dev/null +++ b/src/main/java/org/gridsuite/voltageinit/server/PropertyServerNameProvider.java @@ -0,0 +1,29 @@ +/** + * Copyright (c) 2025, 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.voltageinit.server; + +import com.powsybl.ws.commons.error.ServerNameProvider; +import org.springframework.beans.factory.annotation.Value; +import org.springframework.stereotype.Component; + +/** + * @author Hugo Marcellin + */ +@Component +public class PropertyServerNameProvider implements ServerNameProvider { + + private final String name; + + public PropertyServerNameProvider(@Value("${spring.application.name:voltage-init-server}") String name) { + this.name = name; + } + + @Override + public String serverName() { + return name; + } +} diff --git a/src/main/java/org/gridsuite/voltageinit/server/VoltageInitApplication.java b/src/main/java/org/gridsuite/voltageinit/server/VoltageInitApplication.java index a203e8b..28adb89 100644 --- a/src/main/java/org/gridsuite/voltageinit/server/VoltageInitApplication.java +++ b/src/main/java/org/gridsuite/voltageinit/server/VoltageInitApplication.java @@ -7,6 +7,7 @@ package org.gridsuite.voltageinit.server; import com.powsybl.network.store.client.NetworkStoreService; +import org.gridsuite.computation.error.ComputationExceptionHandler; import org.gridsuite.computation.service.NotificationService; import org.springframework.boot.SpringApplication; import org.springframework.boot.autoconfigure.SpringBootApplication; @@ -15,7 +16,7 @@ * @author Etienne Homer */ @SuppressWarnings("checkstyle:HideUtilityClassConstructor") -@SpringBootApplication(scanBasePackageClasses = { VoltageInitApplication.class, NetworkStoreService.class, NotificationService.class }) +@SpringBootApplication(scanBasePackageClasses = {VoltageInitApplication.class, NetworkStoreService.class, NotificationService.class, ComputationExceptionHandler.class}) public class VoltageInitApplication { public static void main(String[] args) { SpringApplication.run(VoltageInitApplication.class, args); diff --git a/src/main/java/org/gridsuite/voltageinit/server/error/VoltageInitBusinessErrorCode.java b/src/main/java/org/gridsuite/voltageinit/server/error/VoltageInitBusinessErrorCode.java new file mode 100644 index 0000000..d7b5b1f --- /dev/null +++ b/src/main/java/org/gridsuite/voltageinit/server/error/VoltageInitBusinessErrorCode.java @@ -0,0 +1,27 @@ +/** + * Copyright (c) 2017-2025, 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/. + * SPDX-License-Identifier: MPL-2.0 + */ +package org.gridsuite.voltageinit.server.error; + +import com.powsybl.ws.commons.error.BusinessErrorCode; + +/** + * @author Hugo Marcellin + */ +public enum VoltageInitBusinessErrorCode implements BusinessErrorCode { + MISSING_FILTER("voltageInit.missingFilter"); + + private final String code; + + VoltageInitBusinessErrorCode(String code) { + this.code = code; + } + + public String value() { + return code; + } +} diff --git a/src/main/java/org/gridsuite/voltageinit/server/error/VoltageInitException.java b/src/main/java/org/gridsuite/voltageinit/server/error/VoltageInitException.java index 408e6ea..30c5300 100644 --- a/src/main/java/org/gridsuite/voltageinit/server/error/VoltageInitException.java +++ b/src/main/java/org/gridsuite/voltageinit/server/error/VoltageInitException.java @@ -6,14 +6,25 @@ */ package org.gridsuite.voltageinit.server.error; +import com.powsybl.ws.commons.error.AbstractBusinessException; +import lombok.NonNull; + import java.util.Objects; /** * @author Mohamed Ben-rejeb {@literal } */ -public class VoltageInitException extends RuntimeException { +public class VoltageInitException extends AbstractBusinessException { + + private final VoltageInitBusinessErrorCode errorCode; - public VoltageInitException(String message) { + public VoltageInitException(VoltageInitBusinessErrorCode errorCode, String message) { super(Objects.requireNonNull(message, "message must not be null")); + this.errorCode = Objects.requireNonNull(errorCode, "errorCode must not be null"); + } + + @Override + public @NonNull VoltageInitBusinessErrorCode getBusinessErrorCode() { + return errorCode; } } diff --git a/src/main/java/org/gridsuite/voltageinit/server/error/VoltageInitExceptionHandler.java b/src/main/java/org/gridsuite/voltageinit/server/error/VoltageInitExceptionHandler.java new file mode 100644 index 0000000..209deb7 --- /dev/null +++ b/src/main/java/org/gridsuite/voltageinit/server/error/VoltageInitExceptionHandler.java @@ -0,0 +1,45 @@ +/** + * Copyright (c) 2025, 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.voltageinit.server.error; + +import com.powsybl.ws.commons.error.AbstractBusinessExceptionHandler; +import com.powsybl.ws.commons.error.PowsyblWsProblemDetail; +import com.powsybl.ws.commons.error.ServerNameProvider; +import jakarta.servlet.http.HttpServletRequest; +import lombok.NonNull; +import org.springframework.http.HttpStatus; +import org.springframework.http.ResponseEntity; +import org.springframework.web.bind.annotation.ControllerAdvice; +import org.springframework.web.bind.annotation.ExceptionHandler; + +/** + * @author Hugo Marcellin + */ +@ControllerAdvice +public class VoltageInitExceptionHandler extends AbstractBusinessExceptionHandler { + protected VoltageInitExceptionHandler(ServerNameProvider serverNameProvider) { + super(serverNameProvider); + } + + @Override + protected @NonNull VoltageInitBusinessErrorCode getBusinessCode(VoltageInitException e) { + return e.getBusinessErrorCode(); + } + + @Override + protected HttpStatus mapStatus(VoltageInitBusinessErrorCode businessErrorCode) { + return switch (businessErrorCode) { + case MISSING_FILTER -> HttpStatus.INTERNAL_SERVER_ERROR; + }; + } + + @ExceptionHandler(VoltageInitException.class) + protected ResponseEntity handleVoltageInitException( + VoltageInitException exception, HttpServletRequest request) { + return super.handleDomainException(exception, request); + } +} diff --git a/src/main/java/org/gridsuite/voltageinit/server/service/parameters/FilterService.java b/src/main/java/org/gridsuite/voltageinit/server/service/parameters/FilterService.java index 01b0ae5..baa7c6d 100644 --- a/src/main/java/org/gridsuite/voltageinit/server/service/parameters/FilterService.java +++ b/src/main/java/org/gridsuite/voltageinit/server/service/parameters/FilterService.java @@ -15,8 +15,10 @@ import org.gridsuite.filter.AbstractFilter; import org.gridsuite.filter.utils.EquipmentType; import org.gridsuite.voltageinit.server.dto.parameters.FilterEquipments; +import org.gridsuite.voltageinit.server.error.VoltageInitBusinessErrorCode; import org.gridsuite.voltageinit.server.error.VoltageInitException; import org.springframework.beans.factory.annotation.Value; +import org.springframework.boot.web.client.RestTemplateBuilder; import org.springframework.core.ParameterizedTypeReference; import org.springframework.http.HttpMethod; import org.springframework.stereotype.Service; @@ -45,9 +47,10 @@ public class FilterService extends AbstractFilterService { public static final String FILTERS_NOT_FOUND = "Filters not found"; - public FilterService(NetworkStoreService networkStoreService, + public FilterService(RestTemplateBuilder restTemplateBuilder, + NetworkStoreService networkStoreService, @Value("${gridsuite.services.filter-server.base-uri:http://filter-server/}") String filterServerBaseUri) { - super(networkStoreService, filterServerBaseUri); + super(restTemplateBuilder, networkStoreService, filterServerBaseUri); } public List exportFilters(List filtersUuids, UUID networkUuid, String variantId) { @@ -85,7 +88,7 @@ public void ensureFiltersExist(Map filterNamesByUuid) { .filter(filterId -> !validFilters.contains(filterId)) .toList(); if (!missingFilters.isEmpty()) { - throw new VoltageInitException(buildMissingFiltersMessage(missingFilters, filterNamesByUuid)); + throw new VoltageInitException(VoltageInitBusinessErrorCode.MISSING_FILTER, buildMissingFiltersMessage(missingFilters, filterNamesByUuid)); } } diff --git a/src/test/java/org/gridsuite/voltageinit/server/PropertyServerNameProviderTest.java b/src/test/java/org/gridsuite/voltageinit/server/PropertyServerNameProviderTest.java new file mode 100644 index 0000000..b932fc3 --- /dev/null +++ b/src/test/java/org/gridsuite/voltageinit/server/PropertyServerNameProviderTest.java @@ -0,0 +1,23 @@ +/** + * Copyright (c) 2025, 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.voltageinit.server; + +import org.junit.jupiter.api.Test; + +import static org.assertj.core.api.Assertions.assertThat; + +/** + * @author Hugo Marcellin + */ +class PropertyServerNameProviderTest { + + @Test + void returnsProvidedName() { + PropertyServerNameProvider provider = new PropertyServerNameProvider("custom-server"); + assertThat(provider.serverName()).isEqualTo("custom-server"); + } +} diff --git a/src/test/java/org/gridsuite/voltageinit/server/VoltageInitBusinessErrorCodeTest.java b/src/test/java/org/gridsuite/voltageinit/server/VoltageInitBusinessErrorCodeTest.java new file mode 100644 index 0000000..5d667fb --- /dev/null +++ b/src/test/java/org/gridsuite/voltageinit/server/VoltageInitBusinessErrorCodeTest.java @@ -0,0 +1,24 @@ +/** + * Copyright (c) 2025, 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.voltageinit.server; + +import org.gridsuite.voltageinit.server.error.VoltageInitBusinessErrorCode; +import org.junit.jupiter.params.ParameterizedTest; +import org.junit.jupiter.params.provider.EnumSource; + +import static org.gridsuite.voltageinit.utils.assertions.Assertions.assertThat; + +/** + * @author Hugo Marcellin + */ +class VoltageInitBusinessErrorCodeTest { + @ParameterizedTest + @EnumSource(VoltageInitBusinessErrorCode.class) + void valueMatchesEnumName(VoltageInitBusinessErrorCode code) { + assertThat(code.value()).startsWith("voltageInit."); + } +} diff --git a/src/test/java/org/gridsuite/voltageinit/server/error/VoltageInitExceptionHandlerTest.java b/src/test/java/org/gridsuite/voltageinit/server/error/VoltageInitExceptionHandlerTest.java new file mode 100644 index 0000000..cc8aadf --- /dev/null +++ b/src/test/java/org/gridsuite/voltageinit/server/error/VoltageInitExceptionHandlerTest.java @@ -0,0 +1,42 @@ +/** + * Copyright (c) 2025, 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.voltageinit.server.error; + +import com.powsybl.ws.commons.error.PowsyblWsProblemDetail; +import org.junit.jupiter.api.BeforeEach; +import org.junit.jupiter.api.Test; +import org.springframework.http.HttpStatus; +import org.springframework.http.ResponseEntity; +import org.springframework.mock.web.MockHttpServletRequest; + +import static org.assertj.core.api.Assertions.assertThat; +import static org.gridsuite.voltageinit.server.error.VoltageInitBusinessErrorCode.MISSING_FILTER; +import static org.junit.jupiter.api.Assertions.assertEquals; + +/** + * @author Hugo Marcellin + */ +class VoltageInitExceptionHandlerTest { + + private VoltageInitExceptionHandler handler; + + @BeforeEach + void setUp() { + handler = new VoltageInitExceptionHandler(() -> "voltageInit"); + } + + @Test + void mapsInteralErrorBusinessErrorToStatus() { + MockHttpServletRequest request = new MockHttpServletRequest("GET", "/results-endpoint/uuid"); + VoltageInitException exception = new VoltageInitException(MISSING_FILTER, "Bus out of voltage"); + ResponseEntity response = handler.handleVoltageInitException(exception, request); + + assertThat(response.getStatusCode()).isEqualTo(HttpStatus.INTERNAL_SERVER_ERROR); + assertThat(response.getBody()).isNotNull(); + assertEquals("voltageInit.missingFilter", response.getBody().getBusinessErrorCode()); + } +} diff --git a/src/test/java/org/gridsuite/voltageinit/server/service/parameters/FilterServiceTest.java b/src/test/java/org/gridsuite/voltageinit/server/service/parameters/FilterServiceTest.java index 322def0..18ef856 100644 --- a/src/test/java/org/gridsuite/voltageinit/server/service/parameters/FilterServiceTest.java +++ b/src/test/java/org/gridsuite/voltageinit/server/service/parameters/FilterServiceTest.java @@ -14,6 +14,7 @@ import org.junit.jupiter.api.BeforeEach; import org.junit.jupiter.api.Test; import org.mockito.Mockito; +import org.springframework.boot.web.client.RestTemplateBuilder; import java.util.List; import java.util.Optional; @@ -38,7 +39,7 @@ class FilterServiceTest { @BeforeEach void setUp() { - filterService = spy(new FilterService(Mockito.mock(NetworkStoreService.class), "http://filter-server/")); + filterService = spy(new FilterService(Mockito.mock(RestTemplateBuilder.class), Mockito.mock(NetworkStoreService.class), "http://filter-server/")); } @Test diff --git a/src/test/java/org/gridsuite/voltageinit/server/service/parameters/ParametersTest.java b/src/test/java/org/gridsuite/voltageinit/server/service/parameters/ParametersTest.java index 187c9df..e488a7e 100644 --- a/src/test/java/org/gridsuite/voltageinit/server/service/parameters/ParametersTest.java +++ b/src/test/java/org/gridsuite/voltageinit/server/service/parameters/ParametersTest.java @@ -27,6 +27,7 @@ import org.gridsuite.voltageinit.server.entities.parameters.FilterEquipmentsEmbeddable; import org.gridsuite.voltageinit.server.entities.parameters.VoltageInitParametersEntity; import org.gridsuite.voltageinit.server.entities.parameters.VoltageLimitEntity; +import org.gridsuite.voltageinit.server.error.VoltageInitBusinessErrorCode; import org.gridsuite.voltageinit.server.error.VoltageInitException; import org.gridsuite.voltageinit.server.service.VoltageInitRunContext; import org.gridsuite.voltageinit.server.util.EquipmentsSelectionType; @@ -211,7 +212,7 @@ void buildOpenReacParametersThrowsWhenFilterMissing() { .withResourceBundles("i18n.reports") .withMessageTemplate(COMPUTATION_TYPE).build()); - Mockito.doThrow(new VoltageInitException(FilterService.FILTERS_NOT_FOUND + " [" + FILTER_1 + "]")) + Mockito.doThrow(new VoltageInitException(VoltageInitBusinessErrorCode.MISSING_FILTER, FilterService.FILTERS_NOT_FOUND + " [" + FILTER_1 + "]")) .when(filterService) .ensureFiltersExist(Mockito.anyMap());