From 5d0828d4eb2dc3ece6322d09d83bcd9315991c3d Mon Sep 17 00:00:00 2001 From: FriedJannik Date: Thu, 12 Sep 2024 14:56:40 +0200 Subject: [PATCH 01/12] Adds initial Gateway Module This commit includes: - Loading of application.properties configs - ConfigurationGuard for Error Handling - Initial /shells endpoint (stub) --- basyx.gateway/basyx.gateway-core/pom.xml | 21 ++++ basyx.gateway/basyx.gateway-http/pom.xml | 56 +++++++++++ .../basyx/gateway/http/GatewayHTTPApi.java | 68 +++++++++++++ .../http/GatewayHTTPApiController.java | 62 ++++++++++++ basyx.gateway/basyx.gateway.component/pom.xml | 66 +++++++++++++ .../gateway/component/ConfigurationGuard.java | 98 +++++++++++++++++++ .../gateway/component/GatewayComponent.java | 42 ++++++++ .../src/main/resources/application.properties | 14 +++ .../src/main/resources/banner.txt | 7 ++ basyx.gateway/pom.xml | 22 +++++ pom.xml | 15 +++ 11 files changed, 471 insertions(+) create mode 100644 basyx.gateway/basyx.gateway-core/pom.xml create mode 100644 basyx.gateway/basyx.gateway-http/pom.xml create mode 100644 basyx.gateway/basyx.gateway-http/src/main/java/org/eclipse/digitaltwin/basyx/gateway/http/GatewayHTTPApi.java create mode 100644 basyx.gateway/basyx.gateway-http/src/main/java/org/eclipse/digitaltwin/basyx/gateway/http/GatewayHTTPApiController.java create mode 100644 basyx.gateway/basyx.gateway.component/pom.xml create mode 100644 basyx.gateway/basyx.gateway.component/src/main/java/org/eclipse/digitaltwin/basyx/gateway/component/ConfigurationGuard.java create mode 100644 basyx.gateway/basyx.gateway.component/src/main/java/org/eclipse/digitaltwin/basyx/gateway/component/GatewayComponent.java create mode 100644 basyx.gateway/basyx.gateway.component/src/main/resources/application.properties create mode 100644 basyx.gateway/basyx.gateway.component/src/main/resources/banner.txt create mode 100644 basyx.gateway/pom.xml diff --git a/basyx.gateway/basyx.gateway-core/pom.xml b/basyx.gateway/basyx.gateway-core/pom.xml new file mode 100644 index 000000000..1651f53c0 --- /dev/null +++ b/basyx.gateway/basyx.gateway-core/pom.xml @@ -0,0 +1,21 @@ + + + 4.0.0 + + org.eclipse.digitaltwin.basyx + basyx.gateway + ${revision} + + + basyx.gateway-core + BaSyx Gateway HTTP + BaSyx Gateway HTTP + + + org.eclipse.digitaltwin.aas4j + aas4j-model + + + \ No newline at end of file diff --git a/basyx.gateway/basyx.gateway-http/pom.xml b/basyx.gateway/basyx.gateway-http/pom.xml new file mode 100644 index 000000000..e64eff3be --- /dev/null +++ b/basyx.gateway/basyx.gateway-http/pom.xml @@ -0,0 +1,56 @@ + + + 4.0.0 + + org.eclipse.digitaltwin.basyx + basyx.gateway + ${revision} + + + basyx.gateway-http + BaSyx Gateway HTTP + BaSyx Gateway HTTP + + + + org.eclipse.digitaltwin.basyx + basyx.http + + + org.eclipse.digitaltwin.aas4j + aas4j-model + + + org.springframework + spring-context + + + org.springframework.boot + spring-boot-starter-web + + + org.springdoc + springdoc-openapi-starter-webmvc-ui + + + jakarta.validation + jakarta.validation-api + + + org.eclipse.digitaltwin.aas4j + aas4j-dataformat-json + + + org.apache.httpcomponents.client5 + httpclient5 + test + + + commons-io + commons-io + test + + + \ No newline at end of file diff --git a/basyx.gateway/basyx.gateway-http/src/main/java/org/eclipse/digitaltwin/basyx/gateway/http/GatewayHTTPApi.java b/basyx.gateway/basyx.gateway-http/src/main/java/org/eclipse/digitaltwin/basyx/gateway/http/GatewayHTTPApi.java new file mode 100644 index 000000000..3854896bd --- /dev/null +++ b/basyx.gateway/basyx.gateway-http/src/main/java/org/eclipse/digitaltwin/basyx/gateway/http/GatewayHTTPApi.java @@ -0,0 +1,68 @@ +/******************************************************************************* + * Copyright (C) 2024 the Eclipse BaSyx Authors + * + * Permission is hereby granted, free of charge, to any person obtaining + * a copy of this software and associated documentation files (the + * "Software"), to deal in the Software without restriction, including + * without limitation the rights to use, copy, modify, merge, publish, + * distribute, sublicense, and/or sell copies of the Software, and to + * permit persons to whom the Software is furnished to do so, subject to + * the following conditions: + * + * The above copyright notice and this permission notice shall be + * included in all copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, + * EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF + * MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND + * NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE + * LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION + * OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION + * WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. + * + * SPDX-License-Identifier: MIT + ******************************************************************************/ + +package org.eclipse.digitaltwin.basyx.gateway.http; + +import io.swagger.v3.oas.annotations.Operation; +import io.swagger.v3.oas.annotations.Parameter; +import io.swagger.v3.oas.annotations.enums.ParameterIn; +import io.swagger.v3.oas.annotations.media.Content; +import io.swagger.v3.oas.annotations.media.Schema; +import io.swagger.v3.oas.annotations.responses.ApiResponse; +import io.swagger.v3.oas.annotations.responses.ApiResponses; +import jakarta.validation.Valid; +import org.eclipse.digitaltwin.aas4j.v3.model.AssetAdministrationShell; +import org.eclipse.digitaltwin.aas4j.v3.model.Result; +import org.springframework.http.ResponseEntity; +import org.springframework.validation.annotation.Validated; +import org.springframework.web.bind.annotation.*; + +@Validated +public interface GatewayHTTPApi { + + @Operation(summary = "Creates a new Asset Administration Shell", description = "", tags = { "Asset Administration Shell Repository API" }) + @ApiResponses(value = { + @ApiResponse(responseCode = "201", description = "Asset Administration Shell created successfully", content = @Content(mediaType = "application/json", schema = @Schema(implementation = AssetAdministrationShell.class))), + + @ApiResponse(responseCode = "400", description = "Bad Request, e.g. the request parameters of the format of the request body is wrong.", content = @Content(mediaType = "application/json", schema = @Schema(implementation = Result.class))), + + @ApiResponse(responseCode = "401", description = "Unauthorized, e.g. the server refused the authorization attempt.", content = @Content(mediaType = "application/json", schema = @Schema(implementation = Result.class))), + + @ApiResponse(responseCode = "403", description = "Forbidden", content = @Content(mediaType = "application/json", schema = @Schema(implementation = Result.class))), + + @ApiResponse(responseCode = "409", description = "Conflict, a resource which shall be created exists already. Might be thrown if a Submodel or SubmodelElement with the same ShortId is contained in a POST request.", content = @Content(mediaType = "application/json", schema = @Schema(implementation = Result.class))), + + @ApiResponse(responseCode = "500", description = "Internal Server Error", content = @Content(mediaType = "application/json", schema = @Schema(implementation = Result.class))), + + @ApiResponse(responseCode = "200", description = "Default error handling for unmentioned status codes", content = @Content(mediaType = "application/json", schema = @Schema(implementation = Result.class))) }) + @RequestMapping(value = "/shells", produces = { "application/json" }, consumes = { "application/json" }, method = RequestMethod.POST) + ResponseEntity postAssetAdministrationShell( + @Parameter(in = ParameterIn.DEFAULT, description = "Asset Administration Shell object", required = true, schema = @Schema()) @Valid @RequestBody AssetAdministrationShell body, + @Parameter(in = ParameterIn.QUERY, description = "The Asset Administration Shell Repository URL", schema = @Schema()) @RequestParam(value = "aasRepositoryURL", required = false) String aasRepositoryURL, + @Parameter(in = ParameterIn.QUERY, description = "The Asset Administration Shell Repository URL", schema = @Schema()) @RequestParam(value = "aasRegistryURL", required = false) String aasRegistryURL + + ); + +} diff --git a/basyx.gateway/basyx.gateway-http/src/main/java/org/eclipse/digitaltwin/basyx/gateway/http/GatewayHTTPApiController.java b/basyx.gateway/basyx.gateway-http/src/main/java/org/eclipse/digitaltwin/basyx/gateway/http/GatewayHTTPApiController.java new file mode 100644 index 000000000..2587c51fa --- /dev/null +++ b/basyx.gateway/basyx.gateway-http/src/main/java/org/eclipse/digitaltwin/basyx/gateway/http/GatewayHTTPApiController.java @@ -0,0 +1,62 @@ +/******************************************************************************* + * Copyright (C) 2024 the Eclipse BaSyx Authors + * + * Permission is hereby granted, free of charge, to any person obtaining + * a copy of this software and associated documentation files (the + * "Software"), to deal in the Software without restriction, including + * without limitation the rights to use, copy, modify, merge, publish, + * distribute, sublicense, and/or sell copies of the Software, and to + * permit persons to whom the Software is furnished to do so, subject to + * the following conditions: + * + * The above copyright notice and this permission notice shall be + * included in all copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, + * EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF + * MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND + * NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE + * LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION + * OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION + * WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. + * + * SPDX-License-Identifier: MIT + ******************************************************************************/ + +package org.eclipse.digitaltwin.basyx.gateway.http; + +import org.eclipse.digitaltwin.aas4j.v3.model.AssetAdministrationShell; +import org.springframework.beans.factory.annotation.Value; +import org.springframework.boot.autoconfigure.condition.ConditionalOnProperty; +import org.springframework.http.HttpStatus; +import org.springframework.http.ResponseEntity; +import org.springframework.web.bind.annotation.RestController; + +@RestController +@ConditionalOnProperty(name = "basyx.gateway.aas-repository") +public class GatewayHTTPApiController implements GatewayHTTPApi{ + @Value("${basyx.gateway.aas-repository:#{null}}") + public String aasRepositoryURL; + @Value("${basyx.gateway.submodel-repository:#{null}}") + public String submodelRepositoryURL; + @Value("${basyx.gateway.aas-registry:#{null}}") + public String aasRegistryURL; + @Value("${basyx.gateway.submodel-registry:#{null}}") + public String submodelRegistryURL; + + @Override + public ResponseEntity postAssetAdministrationShell(AssetAdministrationShell body, String aasRepositoryURL, String aasRegistryURL) { + if(!isAnyURLSet(aasRepositoryURL, this.aasRepositoryURL)){ + return ResponseEntity.badRequest().body("No AAS Repository URL set"); + + } + + return null; + } + + private static boolean isAnyURLSet(String url1, String url2) { + return url1 != null || url2 != null; + } + + +} diff --git a/basyx.gateway/basyx.gateway.component/pom.xml b/basyx.gateway/basyx.gateway.component/pom.xml new file mode 100644 index 000000000..2042d284d --- /dev/null +++ b/basyx.gateway/basyx.gateway.component/pom.xml @@ -0,0 +1,66 @@ + + + 4.0.0 + + org.eclipse.digitaltwin.basyx + basyx.gateway + ${revision} + + + basyx.gateway.component + BaSyx Gateway Component + BaSyx Gateway Component + + + basyx-gateway + + + + + org.eclipse.digitaltwin.basyx + basyx.gateway-http + + + org.eclipse.digitaltwin.basyx + basyx.http + + + org.apache.httpcomponents.client5 + httpclient5 + test + + + commons-io + commons-io + + + com.google.code.gson + gson + test + + + org.springframework.boot + spring-boot-starter + + + org.springframework.boot + spring-boot-starter-test + test + + + org.junit.vintage + junit-vintage-engine + test + + + org.springframework.boot + spring-boot-starter-web + + + org.springframework.boot + spring-boot-starter-actuator + + + \ No newline at end of file diff --git a/basyx.gateway/basyx.gateway.component/src/main/java/org/eclipse/digitaltwin/basyx/gateway/component/ConfigurationGuard.java b/basyx.gateway/basyx.gateway.component/src/main/java/org/eclipse/digitaltwin/basyx/gateway/component/ConfigurationGuard.java new file mode 100644 index 000000000..d90886a9e --- /dev/null +++ b/basyx.gateway/basyx.gateway.component/src/main/java/org/eclipse/digitaltwin/basyx/gateway/component/ConfigurationGuard.java @@ -0,0 +1,98 @@ +/******************************************************************************* + * Copyright (C) 2024 the Eclipse BaSyx Authors + * + * Permission is hereby granted, free of charge, to any person obtaining + * a copy of this software and associated documentation files (the + * "Software"), to deal in the Software without restriction, including + * without limitation the rights to use, copy, modify, merge, publish, + * distribute, sublicense, and/or sell copies of the Software, and to + * permit persons to whom the Software is furnished to do so, subject to + * the following conditions: + * + * The above copyright notice and this permission notice shall be + * included in all copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, + * EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF + * MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND + * NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE + * LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION + * OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION + * WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. + * + * SPDX-License-Identifier: MIT + ******************************************************************************/ + +package org.eclipse.digitaltwin.basyx.gateway.component; + +import org.springframework.beans.factory.InitializingBean; +import org.springframework.beans.factory.annotation.Value; +import org.springframework.stereotype.Component; + +import java.util.ArrayList; +import java.util.List; +import java.util.stream.Collectors; + +@Component +public class ConfigurationGuard implements InitializingBean { + + @Value("${basyx.gateway.aas-repository:#{null}}") + public String aasRepositoryURL; + @Value("${basyx.gateway.submodel-repository:#{null}}") + public String submodelRepositoryURL; + @Value("${basyx.gateway.aas-registry:#{null}}") + public String aasRegistryURL; + @Value("${basyx.gateway.submodel-registry:#{null}}") + public String submodelRegistryURL; + + @Override + public void afterPropertiesSet() throws Exception { + List missingNonRequiredProperties = getMissingNonRequiredProperties(); + if (!missingNonRequiredProperties.isEmpty()) { + printWarning(missingNonRequiredProperties); + } + + System.out.println("\n:::::::::::::::: BaSyx Gateway Configuration ::::::::::::::::"); + if (aasRepositoryURL != null) { + System.out.println(":: Default AAS Repository URL: " + aasRepositoryURL); + } + if (submodelRepositoryURL != null) { + System.out.println(":: Default Submodel Repository URL: " + submodelRepositoryURL); + } + if (aasRegistryURL != null) { + System.out.println(":: Default AAS Registry URL: " + aasRegistryURL); + } + if (submodelRegistryURL != null) { + System.out.println(":: Default Submodel Registry URL: " + submodelRegistryURL); + } + System.out.println(":::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::\n"); + } + + + private static void printWarning(List missingNonRequiredProperties) { + System.err.println( + "\n:::::: BaSyx Gateway Startup Warning ::::::" + + "\n\nThe following, recommended, properties are missing: \n" + + missingNonRequiredProperties.stream() + .map(prop -> "- " + prop) + .collect(Collectors.joining("\n"))+"\n" + ); + } + + private List getMissingNonRequiredProperties() { + List missingNonRequiredProperties = new ArrayList<>(); + if(this.aasRegistryURL == null) { + missingNonRequiredProperties.add("basyx.gateway.aas-registry"); + } + if(this.submodelRegistryURL == null) { + missingNonRequiredProperties.add("basyx.gateway.submodel-registry"); + } + if(this.aasRepositoryURL == null) { + missingNonRequiredProperties.add("basyx.gateway.aas-repository"); + } + if(this.submodelRegistryURL == null) { + missingNonRequiredProperties.add("basyx.gateway.submodel-repository"); + } + return missingNonRequiredProperties; + } +} diff --git a/basyx.gateway/basyx.gateway.component/src/main/java/org/eclipse/digitaltwin/basyx/gateway/component/GatewayComponent.java b/basyx.gateway/basyx.gateway.component/src/main/java/org/eclipse/digitaltwin/basyx/gateway/component/GatewayComponent.java new file mode 100644 index 000000000..2b38e77b3 --- /dev/null +++ b/basyx.gateway/basyx.gateway.component/src/main/java/org/eclipse/digitaltwin/basyx/gateway/component/GatewayComponent.java @@ -0,0 +1,42 @@ +/******************************************************************************* + * Copyright (C) 2024 the Eclipse BaSyx Authors + * + * Permission is hereby granted, free of charge, to any person obtaining + * a copy of this software and associated documentation files (the + * "Software"), to deal in the Software without restriction, including + * without limitation the rights to use, copy, modify, merge, publish, + * distribute, sublicense, and/or sell copies of the Software, and to + * permit persons to whom the Software is furnished to do so, subject to + * the following conditions: + * + * The above copyright notice and this permission notice shall be + * included in all copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, + * EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF + * MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND + * NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE + * LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION + * OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION + * WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. + * + * SPDX-License-Identifier: MIT + ******************************************************************************/ + +package org.eclipse.digitaltwin.basyx.gateway.component; + +import org.springframework.beans.factory.annotation.Value; +import org.springframework.boot.SpringApplication; +import org.springframework.boot.autoconfigure.SpringBootApplication; +import org.springframework.boot.autoconfigure.data.mongo.MongoDataAutoConfiguration; +import org.springframework.boot.autoconfigure.mongo.MongoAutoConfiguration; +import org.springframework.context.ConfigurableApplicationContext; + +@SpringBootApplication( + scanBasePackages = "org.eclipse.digitaltwin.basyx", + exclude = { MongoAutoConfiguration.class, MongoDataAutoConfiguration.class }) +public class GatewayComponent { + public static void main(String[] args) { + SpringApplication.run(GatewayComponent.class, args); + } +} diff --git a/basyx.gateway/basyx.gateway.component/src/main/resources/application.properties b/basyx.gateway/basyx.gateway.component/src/main/resources/application.properties new file mode 100644 index 000000000..2e191e7ee --- /dev/null +++ b/basyx.gateway/basyx.gateway.component/src/main/resources/application.properties @@ -0,0 +1,14 @@ +server.port=5999 + + +####################################################### +# AAS/Submodel Repository URL (optional, recommended) # +####################################################### +basyx.gateway.aas-repository=http://localhost:8081 +basyx.gateway.submodel-repository=http://localhost:8082 + +############################################################# +# Default AAS/Submodel Registry URL (optional, recommended) # +############################################################# +basyx.gateway.aas-registry=http://localhost:8083 +basyx.gateway.submodel-registry=http://localhost:8084 diff --git a/basyx.gateway/basyx.gateway.component/src/main/resources/banner.txt b/basyx.gateway/basyx.gateway.component/src/main/resources/banner.txt new file mode 100644 index 000000000..3cfa60cfb --- /dev/null +++ b/basyx.gateway/basyx.gateway.component/src/main/resources/banner.txt @@ -0,0 +1,7 @@ + ____ ____ ____ _ +| __ ) __ _/ ___| _ ___ __ / ___| __ _| |_ _____ ____ _ _ _ +| _ \ / _` \___ \| | | \ \/ / | | _ / _` | __/ _ \ \ /\ / / _` | | | | +| |_) | (_| |___) | |_| |> < | |_| | (_| | || __/\ V V / (_| | |_| | +|____/ \__,_|____/ \__, /_/\_\ \____|\__,_|\__\___| \_/\_/ \__,_|\__, | +===================|___/==========================================|___/ +2.0.0-SNAPSHOT diff --git a/basyx.gateway/pom.xml b/basyx.gateway/pom.xml new file mode 100644 index 000000000..fd2280694 --- /dev/null +++ b/basyx.gateway/pom.xml @@ -0,0 +1,22 @@ + + + 4.0.0 + + org.eclipse.digitaltwin.basyx + basyx.parent + ${revision} + + + basyx.gateway + BaSyx Gateway + Component handling Registry Integration for distributed systems + pom + + + basyx.gateway-http + basyx.gateway-core + basyx.gateway.component + + \ No newline at end of file diff --git a/pom.xml b/pom.xml index ea8092ff2..c781e8b55 100644 --- a/pom.xml +++ b/pom.xml @@ -24,6 +24,7 @@ basyx.conceptdescriptionrepository basyx.aasxfileserver basyx.aasdiscoveryservice + basyx.gateway BaSyx Parent Parent POM for Eclipse BaSyx @@ -768,6 +769,12 @@ basyx.submodelregistry-client-native ${revision} + + + org.eclipse.digitaltwin.basyx + basyx.gateway-http + ${revision} + @@ -1206,6 +1213,14 @@ ${revision} tests + + + org.eclipse.digitaltwin.basyx + basyx.gateway-http + ${revision} + tests + + org.eclipse.digitaltwin.basyx basyx.client From 1627a40bc0657ff6ce089ed3a22a5c11ab5caac6 Mon Sep 17 00:00:00 2001 From: FriedJannik Date: Mon, 16 Sep 2024 10:18:57 +0200 Subject: [PATCH 02/12] Adds draft of createAAS Method with tests --- basyx.gateway/basyx.gateway-core/pom.xml | 16 +++ .../basyx/gateway/core/DefaultGateway.java | 108 +++++++++++++++ .../basyx/gateway/core/GatewayFactory.java | 8 ++ .../BaSyxComponentNotHealthyException.java | 33 +++++ .../gateway/core/feature/GatewayFeature.java | 8 ++ .../basyx/gateway/TestGateway.java | 125 ++++++++++++++++++ basyx.gateway/basyx.gateway.component/pom.xml | 8 +- .../gateway/component/ConfigurationGuard.java | 15 ++- .../gateway/component/GatewayComponent.java | 2 - .../component/GatewayFeaturePrinter.java | 59 +++++++++ pom.xml | 23 +++- 11 files changed, 394 insertions(+), 11 deletions(-) create mode 100644 basyx.gateway/basyx.gateway-core/src/main/java/org/eclipse/digitaltwin/basyx/gateway/core/DefaultGateway.java create mode 100644 basyx.gateway/basyx.gateway-core/src/main/java/org/eclipse/digitaltwin/basyx/gateway/core/GatewayFactory.java create mode 100644 basyx.gateway/basyx.gateway-core/src/main/java/org/eclipse/digitaltwin/basyx/gateway/core/exception/BaSyxComponentNotHealthyException.java create mode 100644 basyx.gateway/basyx.gateway-core/src/main/java/org/eclipse/digitaltwin/basyx/gateway/core/feature/GatewayFeature.java create mode 100644 basyx.gateway/basyx.gateway-core/src/test/java/org/eclipse/digitaltwin/basyx/gateway/TestGateway.java create mode 100644 basyx.gateway/basyx.gateway.component/src/main/java/org/eclipse/digitaltwin/basyx/gateway/component/GatewayFeaturePrinter.java diff --git a/basyx.gateway/basyx.gateway-core/pom.xml b/basyx.gateway/basyx.gateway-core/pom.xml index 1651f53c0..ad04353e5 100644 --- a/basyx.gateway/basyx.gateway-core/pom.xml +++ b/basyx.gateway/basyx.gateway-core/pom.xml @@ -13,9 +13,25 @@ BaSyx Gateway HTTP BaSyx Gateway HTTP + + org.eclipse.digitaltwin.basyx + basyx.core + org.eclipse.digitaltwin.aas4j aas4j-model + + org.mock-server + mockserver-netty + 5.15.0 + test + + + org.mock-server + mockserver-client-java + 5.15.0 + test + \ No newline at end of file diff --git a/basyx.gateway/basyx.gateway-core/src/main/java/org/eclipse/digitaltwin/basyx/gateway/core/DefaultGateway.java b/basyx.gateway/basyx.gateway-core/src/main/java/org/eclipse/digitaltwin/basyx/gateway/core/DefaultGateway.java new file mode 100644 index 000000000..d840c9fd6 --- /dev/null +++ b/basyx.gateway/basyx.gateway-core/src/main/java/org/eclipse/digitaltwin/basyx/gateway/core/DefaultGateway.java @@ -0,0 +1,108 @@ +/******************************************************************************* + * Copyright (C) 2024 the Eclipse BaSyx Authors + * + * Permission is hereby granted, free of charge, to any person obtaining + * a copy of this software and associated documentation files (the + * "Software"), to deal in the Software without restriction, including + * without limitation the rights to use, copy, modify, merge, publish, + * distribute, sublicense, and/or sell copies of the Software, and to + * permit persons to whom the Software is furnished to do so, subject to + * the following conditions: + * + * The above copyright notice and this permission notice shall be + * included in all copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, + * EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF + * MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND + * NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE + * LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION + * OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION + * WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. + * + * SPDX-License-Identifier: MIT + ******************************************************************************/ + +package org.eclipse.digitaltwin.basyx.gateway.core; + +import org.eclipse.digitaltwin.aas4j.v3.model.AssetAdministrationShell; +import org.eclipse.digitaltwin.basyx.gateway.core.exception.BaSyxComponentNotHealthyException; +import org.eclipse.digitaltwin.basyx.gateway.core.feature.Gateway; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; + +import java.io.IOException; +import java.net.HttpURLConnection; +import java.net.URL; + +public class DefaultGateway implements Gateway { + + private Logger logger = LoggerFactory.getLogger(DefaultGateway.class); + + @Override + public void createAAS(AssetAdministrationShell aas, String aasRepository, String aasRegistry) throws BaSyxComponentNotHealthyException{ + throwExceptionIfIsUnhealthyBaSyxComponent(aasRepository); + if(aasRegistry != null){ + throwExceptionIfIsUnhealthyBaSyxComponent(aasRegistry); + } + } + + private void throwExceptionIfIsUnhealthyBaSyxComponent(String componentURL) { + componentURL = formatURL(componentURL); + + if (isBaSyxComponent(componentURL) && !isHealthy(componentURL)) { + throw new BaSyxComponentNotHealthyException(componentURL + " is not healthy"); + } + } + + private String formatURL(String componentURL) { + try { + URL url = new URL(componentURL); + String protocol = url.getProtocol(); + String host = url.getHost(); + int port = url.getPort(); + if (port == -1) { // no port specified, use default port + port = url.getDefaultPort(); + } + componentURL = protocol + "://" + host + ":" + port; + } catch (Exception e) { + logger.error(e.getMessage()); + } + return componentURL; + } + + private boolean isBaSyxComponent(String componentURL) { + try { + HttpURLConnection connection = getRequest(componentURL, "/shells"); + + String aasMiddleware = connection.getHeaderField("AASMiddleware"); + + return "BaSyx".equals(aasMiddleware); + } catch (Exception e) { + logger.error(e.getMessage()); + return false; + } + } + + private boolean isHealthy(String componentURL){ + try { + HttpURLConnection connection = getRequest(componentURL, "/actuator/health"); + + String body = new String(connection.getInputStream().readAllBytes()); + + return connection.getResponseCode() == 200 && body.equals("{'status':'UP'}"); + + } catch (Exception e) { + logger.error(e.getMessage()); + return false; + } + } + + private static HttpURLConnection getRequest(String componentURL, String path) throws IOException { + URL url = new URL(componentURL + path); + HttpURLConnection connection = (HttpURLConnection) url.openConnection(); + connection.setRequestMethod("GET"); + connection.connect(); + return connection; + } +} diff --git a/basyx.gateway/basyx.gateway-core/src/main/java/org/eclipse/digitaltwin/basyx/gateway/core/GatewayFactory.java b/basyx.gateway/basyx.gateway-core/src/main/java/org/eclipse/digitaltwin/basyx/gateway/core/GatewayFactory.java new file mode 100644 index 000000000..b3683552e --- /dev/null +++ b/basyx.gateway/basyx.gateway-core/src/main/java/org/eclipse/digitaltwin/basyx/gateway/core/GatewayFactory.java @@ -0,0 +1,8 @@ +package org.eclipse.digitaltwin.basyx.gateway.core; + +import org.eclipse.digitaltwin.basyx.gateway.core.feature.Gateway; + +public interface GatewayFactory { + public Gateway create(); + +} diff --git a/basyx.gateway/basyx.gateway-core/src/main/java/org/eclipse/digitaltwin/basyx/gateway/core/exception/BaSyxComponentNotHealthyException.java b/basyx.gateway/basyx.gateway-core/src/main/java/org/eclipse/digitaltwin/basyx/gateway/core/exception/BaSyxComponentNotHealthyException.java new file mode 100644 index 000000000..493e25cc5 --- /dev/null +++ b/basyx.gateway/basyx.gateway-core/src/main/java/org/eclipse/digitaltwin/basyx/gateway/core/exception/BaSyxComponentNotHealthyException.java @@ -0,0 +1,33 @@ +/******************************************************************************* + * Copyright (C) 2024 the Eclipse BaSyx Authors + * + * Permission is hereby granted, free of charge, to any person obtaining + * a copy of this software and associated documentation files (the + * "Software"), to deal in the Software without restriction, including + * without limitation the rights to use, copy, modify, merge, publish, + * distribute, sublicense, and/or sell copies of the Software, and to + * permit persons to whom the Software is furnished to do so, subject to + * the following conditions: + * + * The above copyright notice and this permission notice shall be + * included in all copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, + * EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF + * MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND + * NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE + * LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION + * OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION + * WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. + * + * SPDX-License-Identifier: MIT + ******************************************************************************/ + +package org.eclipse.digitaltwin.basyx.gateway.core.exception; + +public class BaSyxComponentNotHealthyException extends RuntimeException{ + + public BaSyxComponentNotHealthyException(String message) { + super(message); + } +} diff --git a/basyx.gateway/basyx.gateway-core/src/main/java/org/eclipse/digitaltwin/basyx/gateway/core/feature/GatewayFeature.java b/basyx.gateway/basyx.gateway-core/src/main/java/org/eclipse/digitaltwin/basyx/gateway/core/feature/GatewayFeature.java new file mode 100644 index 000000000..2298e211c --- /dev/null +++ b/basyx.gateway/basyx.gateway-core/src/main/java/org/eclipse/digitaltwin/basyx/gateway/core/feature/GatewayFeature.java @@ -0,0 +1,8 @@ +package org.eclipse.digitaltwin.basyx.gateway.core.feature; + +import org.eclipse.digitaltwin.basyx.core.BaSyxFeature; +import org.eclipse.digitaltwin.basyx.gateway.core.GatewayFactory; + +public interface GatewayFeature extends BaSyxFeature { + +} diff --git a/basyx.gateway/basyx.gateway-core/src/test/java/org/eclipse/digitaltwin/basyx/gateway/TestGateway.java b/basyx.gateway/basyx.gateway-core/src/test/java/org/eclipse/digitaltwin/basyx/gateway/TestGateway.java new file mode 100644 index 000000000..e0d23a0a3 --- /dev/null +++ b/basyx.gateway/basyx.gateway-core/src/test/java/org/eclipse/digitaltwin/basyx/gateway/TestGateway.java @@ -0,0 +1,125 @@ +/******************************************************************************* + * Copyright (C) 2024 the Eclipse BaSyx Authors + * + * Permission is hereby granted, free of charge, to any person obtaining + * a copy of this software and associated documentation files (the + * "Software"), to deal in the Software without restriction, including + * without limitation the rights to use, copy, modify, merge, publish, + * distribute, sublicense, and/or sell copies of the Software, and to + * permit persons to whom the Software is furnished to do so, subject to + * the following conditions: + * + * The above copyright notice and this permission notice shall be + * included in all copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, + * EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF + * MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND + * NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE + * LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION + * OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION + * WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. + * + * SPDX-License-Identifier: MIT + ******************************************************************************/ + +package org.eclipse.digitaltwin.basyx.gateway; + +import org.apache.http.HttpStatus; +import org.eclipse.digitaltwin.basyx.gateway.core.DefaultGateway; +import org.eclipse.digitaltwin.basyx.gateway.core.exception.BaSyxComponentNotHealthyException; +import org.eclipse.digitaltwin.basyx.gateway.core.feature.Gateway; +import org.junit.After; +import org.junit.Before; +import org.junit.BeforeClass; +import org.junit.Test; +import org.mockserver.client.MockServerClient; +import org.mockserver.integration.ClientAndServer; +import org.mockserver.verify.VerificationTimes; + +public class TestGateway { + + public static ClientAndServer mockBaSyxAasRepository; + public static ClientAndServer mockAasRegistry; + private Gateway gateway; + + @Before + public void setUp(){ + mockBaSyxAasRepository = ClientAndServer.startClientAndServer(6006); + gateway = new DefaultGateway(); + } + + @After + public void tearDown(){ + mockBaSyxAasRepository.stop(); + } + + @Test + public void testCreateAASWithHealthyBaSyxComponent(){ + new MockServerClient("localhost", 6006) + .when(org.mockserver.model.HttpRequest.request() + .withMethod("GET") + .withPath("/shells")) + .respond(org.mockserver.model.HttpResponse.response() + .withStatusCode(HttpStatus.SC_OK) + .withHeader("AASMiddleware","BaSyx") + .withBody("{}")); + new MockServerClient("localhost", 6006) + .when(org.mockserver.model.HttpRequest.request() + .withMethod("GET") + .withPath("/actuator/health")) + .respond(org.mockserver.model.HttpResponse.response() + .withStatusCode(HttpStatus.SC_OK) + .withBody("{'status':'UP'}")); + gateway.createAAS(null, "http://localhost:6006", null); + verifyCall("/shells",1); + verifyCall("/actuator/health",1); + } + + @Test(expected = BaSyxComponentNotHealthyException.class) + public void testCreateAASWithUnhealthyBaSyxComponent(){ + new MockServerClient("localhost", 6006) + .when(org.mockserver.model.HttpRequest.request() + .withMethod("GET") + .withPath("/shells")) + .respond(org.mockserver.model.HttpResponse.response() + .withStatusCode(HttpStatus.SC_OK) + .withHeader("AASMiddleware","BaSyx") + .withBody("{}")); + new MockServerClient("localhost", 6006) + .when(org.mockserver.model.HttpRequest.request() + .withMethod("GET") + .withPath("/actuator/health")) + .respond(org.mockserver.model.HttpResponse.response() + .withStatusCode(HttpStatus.SC_OK) + .withBody("{'status':'DOWN'}")); + gateway.createAAS(null, "http://localhost:6006", null); + } + + @Test + public void testCreateAASWithNonBaSyxComponent(){ + new MockServerClient("localhost", 6006) + .when(org.mockserver.model.HttpRequest.request() + .withMethod("GET") + .withPath("/shells")) + .respond(org.mockserver.model.HttpResponse.response() + .withStatusCode(HttpStatus.SC_OK) + .withBody("{}")); + new MockServerClient("localhost", 6006) + .when(org.mockserver.model.HttpRequest.request() + .withMethod("GET") + .withPath("/actuator/health")) + .respond(org.mockserver.model.HttpResponse.response() + .withStatusCode(HttpStatus.SC_OK) + .withBody("{'status':'UP'}")); + gateway.createAAS(null, "http://localhost:6006", null); + verifyCall("/shells",1); + verifyCall("/actuator/health",0); + } + + private void verifyCall(String path, int timesCalled) { + new MockServerClient("localhost", 6006).verify(org.mockserver.model.HttpRequest.request().withMethod("GET") + .withPath(path) + , VerificationTimes.exactly(timesCalled)); + } +} diff --git a/basyx.gateway/basyx.gateway.component/pom.xml b/basyx.gateway/basyx.gateway.component/pom.xml index 2042d284d..8c2c1c219 100644 --- a/basyx.gateway/basyx.gateway.component/pom.xml +++ b/basyx.gateway/basyx.gateway.component/pom.xml @@ -19,8 +19,12 @@ - org.eclipse.digitaltwin.basyx - basyx.gateway-http + org.eclipse.digitaltwin.basyx + basyx.gateway-core + + + org.eclipse.digitaltwin.basyx + basyx.gateway-http org.eclipse.digitaltwin.basyx diff --git a/basyx.gateway/basyx.gateway.component/src/main/java/org/eclipse/digitaltwin/basyx/gateway/component/ConfigurationGuard.java b/basyx.gateway/basyx.gateway.component/src/main/java/org/eclipse/digitaltwin/basyx/gateway/component/ConfigurationGuard.java index d90886a9e..8d9e9cdc3 100644 --- a/basyx.gateway/basyx.gateway.component/src/main/java/org/eclipse/digitaltwin/basyx/gateway/component/ConfigurationGuard.java +++ b/basyx.gateway/basyx.gateway.component/src/main/java/org/eclipse/digitaltwin/basyx/gateway/component/ConfigurationGuard.java @@ -25,6 +25,8 @@ package org.eclipse.digitaltwin.basyx.gateway.component; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; import org.springframework.beans.factory.InitializingBean; import org.springframework.beans.factory.annotation.Value; import org.springframework.stereotype.Component; @@ -35,6 +37,7 @@ @Component public class ConfigurationGuard implements InitializingBean { + private static final Logger logger = LoggerFactory.getLogger(ConfigurationGuard.class); @Value("${basyx.gateway.aas-repository:#{null}}") public String aasRepositoryURL; @@ -52,20 +55,20 @@ public void afterPropertiesSet() throws Exception { printWarning(missingNonRequiredProperties); } - System.out.println("\n:::::::::::::::: BaSyx Gateway Configuration ::::::::::::::::"); + logger.info("\n:::::::::::::::: BaSyx Gateway Configuration ::::::::::::::::"); if (aasRepositoryURL != null) { - System.out.println(":: Default AAS Repository URL: " + aasRepositoryURL); + logger.info(":: Default AAS Repository URL: " + aasRepositoryURL); } if (submodelRepositoryURL != null) { - System.out.println(":: Default Submodel Repository URL: " + submodelRepositoryURL); + logger.info(":: Default Submodel Repository URL: " + submodelRepositoryURL); } if (aasRegistryURL != null) { - System.out.println(":: Default AAS Registry URL: " + aasRegistryURL); + logger.info(":: Default AAS Registry URL: " + aasRegistryURL); } if (submodelRegistryURL != null) { - System.out.println(":: Default Submodel Registry URL: " + submodelRegistryURL); + logger.info(":: Default Submodel Registry URL: " + submodelRegistryURL); } - System.out.println(":::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::\n"); + logger.info(":::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::\n"); } diff --git a/basyx.gateway/basyx.gateway.component/src/main/java/org/eclipse/digitaltwin/basyx/gateway/component/GatewayComponent.java b/basyx.gateway/basyx.gateway.component/src/main/java/org/eclipse/digitaltwin/basyx/gateway/component/GatewayComponent.java index 2b38e77b3..e6cbd594a 100644 --- a/basyx.gateway/basyx.gateway.component/src/main/java/org/eclipse/digitaltwin/basyx/gateway/component/GatewayComponent.java +++ b/basyx.gateway/basyx.gateway.component/src/main/java/org/eclipse/digitaltwin/basyx/gateway/component/GatewayComponent.java @@ -25,12 +25,10 @@ package org.eclipse.digitaltwin.basyx.gateway.component; -import org.springframework.beans.factory.annotation.Value; import org.springframework.boot.SpringApplication; import org.springframework.boot.autoconfigure.SpringBootApplication; import org.springframework.boot.autoconfigure.data.mongo.MongoDataAutoConfiguration; import org.springframework.boot.autoconfigure.mongo.MongoAutoConfiguration; -import org.springframework.context.ConfigurableApplicationContext; @SpringBootApplication( scanBasePackages = "org.eclipse.digitaltwin.basyx", diff --git a/basyx.gateway/basyx.gateway.component/src/main/java/org/eclipse/digitaltwin/basyx/gateway/component/GatewayFeaturePrinter.java b/basyx.gateway/basyx.gateway.component/src/main/java/org/eclipse/digitaltwin/basyx/gateway/component/GatewayFeaturePrinter.java new file mode 100644 index 000000000..982868834 --- /dev/null +++ b/basyx.gateway/basyx.gateway.component/src/main/java/org/eclipse/digitaltwin/basyx/gateway/component/GatewayFeaturePrinter.java @@ -0,0 +1,59 @@ +/******************************************************************************* + * Copyright (C) 2023 the Eclipse BaSyx Authors + * + * Permission is hereby granted, free of charge, to any person obtaining + * a copy of this software and associated documentation files (the + * "Software"), to deal in the Software without restriction, including + * without limitation the rights to use, copy, modify, merge, publish, + * distribute, sublicense, and/or sell copies of the Software, and to + * permit persons to whom the Software is furnished to do so, subject to + * the following conditions: + * + * The above copyright notice and this permission notice shall be + * included in all copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, + * EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF + * MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND + * NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE + * LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION + * OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION + * WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. + * + * SPDX-License-Identifier: MIT + ******************************************************************************/ + + +package org.eclipse.digitaltwin.basyx.gateway.component; +import java.util.List; + +import org.eclipse.digitaltwin.basyx.gateway.core.feature.GatewayFeature; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.stereotype.Service; + +/** + * Prints all SubmodelRepository features that are on the classpath + * + * @author schnicke + * + */ +@Service +public class GatewayFeaturePrinter { + + private static final Logger logger = LoggerFactory.getLogger(GatewayFeaturePrinter.class); + + @Autowired + public GatewayFeaturePrinter(List features) { + logger.info("-------------------- BaSyx Gateway Features: --------------------"); + if(features.isEmpty()){ + logger.info("No BaSyx Gateway Features found"); + } + for (GatewayFeature feature : features) { + logger.info("BaSyxFeature " + feature.getName() + " is enabled: " + feature.isEnabled()); + } + + logger.info("----------------------------------------------------------------- "); + } +} diff --git a/pom.xml b/pom.xml index 1ca6dd76e..eb45444cb 100644 --- a/pom.xml +++ b/pom.xml @@ -780,6 +780,16 @@ basyx.gateway-http ${revision} + + org.eclipse.digitaltwin.basyx + basyx.gateway.component + ${revision} + + + org.eclipse.digitaltwin.basyx + basyx.gateway-core + ${revision} + @@ -1225,7 +1235,18 @@ ${revision} tests - + + org.eclipse.digitaltwin.basyx + basyx.gateway.component + ${revision} + tests + + + org.eclipse.digitaltwin.basyx + basyx.gateway-core + ${revision} + tests + org.eclipse.digitaltwin.basyx basyx.client From 849f2fa9611d7427ba7c3cebd352c43426b7fabd Mon Sep 17 00:00:00 2001 From: FriedJannik Date: Mon, 16 Sep 2024 10:19:41 +0200 Subject: [PATCH 03/12] Adds missing interface class --- .../eclipse/digitaltwin/basyx/gateway/core/Gateway.java | 9 +++++++++ 1 file changed, 9 insertions(+) create mode 100644 basyx.gateway/basyx.gateway-core/src/main/java/org/eclipse/digitaltwin/basyx/gateway/core/Gateway.java diff --git a/basyx.gateway/basyx.gateway-core/src/main/java/org/eclipse/digitaltwin/basyx/gateway/core/Gateway.java b/basyx.gateway/basyx.gateway-core/src/main/java/org/eclipse/digitaltwin/basyx/gateway/core/Gateway.java new file mode 100644 index 000000000..dad0d9be7 --- /dev/null +++ b/basyx.gateway/basyx.gateway-core/src/main/java/org/eclipse/digitaltwin/basyx/gateway/core/Gateway.java @@ -0,0 +1,9 @@ +package org.eclipse.digitaltwin.basyx.gateway.core.feature; + +import org.eclipse.digitaltwin.aas4j.v3.model.AssetAdministrationShell; +import org.eclipse.digitaltwin.basyx.gateway.core.exception.BaSyxComponentNotHealthyException; + +public interface Gateway { + + public void createAAS(AssetAdministrationShell aas, String aasRepository, String aasRegistry) throws BaSyxComponentNotHealthyException; +} From 6e94720d0aa23c70b79671a53733dd6e575ff891 Mon Sep 17 00:00:00 2001 From: FriedJannik Date: Mon, 16 Sep 2024 10:23:40 +0200 Subject: [PATCH 04/12] Adds Gateway to Workflow --- .github/workflows/basyx_test.yml | 20 ++++++++++++++++++++ .github/workflows/docker_test.yml | 26 ++++++++++++++++++++++++++ 2 files changed, 46 insertions(+) diff --git a/.github/workflows/basyx_test.yml b/.github/workflows/basyx_test.yml index 1187244a2..7ab43c692 100644 --- a/.github/workflows/basyx_test.yml +++ b/.github/workflows/basyx_test.yml @@ -243,3 +243,23 @@ jobs: - name: Stop environment if: always() run: docker compose --project-directory ./ci down + test-basyx-gateway: + runs-on: ubuntu-latest + name: AAS Discovery Service Test + steps: + - uses: actions/checkout@v4 + - name: Set up JDK 17 + uses: actions/setup-java@v4 + with: + java-version: '17' + distribution: 'adopt' + cache: maven + - name: Start environment + run: docker compose --project-directory ./ci up -d --wait + - name: Build BaSyx + run: mvn clean install ${MVN_ARGS_BUILD_BASYX} + - name: Test AAS Discovery Service + run: mvn test -f "basyx.gateway/pom.xml" + - name: Stop environment + if: always() + run: docker compose --project-directory ./ci down diff --git a/.github/workflows/docker_test.yml b/.github/workflows/docker_test.yml index d10df98fa..8cca8366a 100644 --- a/.github/workflows/docker_test.yml +++ b/.github/workflows/docker_test.yml @@ -415,3 +415,29 @@ jobs: - name: Clean up run: exit 0 + + build-test-gateway: + runs-on: ubuntu-latest + name: Submodel Repository - Build and Start Docker Image + steps: + - uses: actions/checkout@v4 + + - name: Set up JDK 17 + uses: actions/setup-java@v4 + with: + java-version: '17' + distribution: 'adopt' + cache: maven + - name: Build BaSyx + run: | + mvn clean install ${MVN_ARGS_BUILD_BASYX_NO_TESTS} + + - name: Build Gateway Docker Image + run: | + mvn package -DskipTests -Ddocker.namespace=test --pl "org.eclipse.digitaltwin.basyx:basyx.gateway.component" + + - name: Test Gateway Docker Image + run: chmod +x ./.github/workflows/scripts/build_start_docker_image.sh && ./.github/workflows/scripts/build_start_docker_image.sh test/basyx-gateway ${VERSION} test_basyx_gateway + + - name: Clean up + run: exit 0 From d1d65cfae43a33cd104a61fae750114674d99ce4 Mon Sep 17 00:00:00 2001 From: FriedJannik Date: Mon, 16 Sep 2024 10:25:37 +0200 Subject: [PATCH 05/12] Changes false names of workflows --- .github/workflows/basyx_test.yml | 2 +- .github/workflows/docker_test.yml | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/.github/workflows/basyx_test.yml b/.github/workflows/basyx_test.yml index 7ab43c692..9f49ffb5a 100644 --- a/.github/workflows/basyx_test.yml +++ b/.github/workflows/basyx_test.yml @@ -245,7 +245,7 @@ jobs: run: docker compose --project-directory ./ci down test-basyx-gateway: runs-on: ubuntu-latest - name: AAS Discovery Service Test + name: Gateway Test steps: - uses: actions/checkout@v4 - name: Set up JDK 17 diff --git a/.github/workflows/docker_test.yml b/.github/workflows/docker_test.yml index 8cca8366a..f00e1cca3 100644 --- a/.github/workflows/docker_test.yml +++ b/.github/workflows/docker_test.yml @@ -418,7 +418,7 @@ jobs: build-test-gateway: runs-on: ubuntu-latest - name: Submodel Repository - Build and Start Docker Image + name: Gateway - Build and Start Docker Image steps: - uses: actions/checkout@v4 From fb6fd3c0203927e46165ac76f16d9184ed2e71e3 Mon Sep 17 00:00:00 2001 From: FriedJannik Date: Mon, 16 Sep 2024 10:49:05 +0200 Subject: [PATCH 06/12] Adds missing Dockerfile --- basyx.gateway/basyx.gateway.component/Dockerfile | 11 +++++++++++ 1 file changed, 11 insertions(+) create mode 100644 basyx.gateway/basyx.gateway.component/Dockerfile diff --git a/basyx.gateway/basyx.gateway.component/Dockerfile b/basyx.gateway/basyx.gateway.component/Dockerfile new file mode 100644 index 000000000..850188a4c --- /dev/null +++ b/basyx.gateway/basyx.gateway.component/Dockerfile @@ -0,0 +1,11 @@ +FROM amazoncorretto:17 +USER nobody +WORKDIR /application +ARG JAR_FILE=target/*-exec.jar +COPY ${JAR_FILE} basyxExecutable.jar +COPY src/main/resources/application.properties application.properties +ARG PORT=8081 +ENV SERVER_PORT=${PORT} +EXPOSE ${SERVER_PORT} +HEALTHCHECK --interval=30s --timeout=3s --retries=3 --start-period=30s CMD curl --fail http://localhost:${SERVER_PORT}/actuator/health || exit 1 +ENTRYPOINT ["java","-jar","basyxExecutable.jar"] From cfb6e1aec9bc9617af6ea999aaa8743002815d03 Mon Sep 17 00:00:00 2001 From: FriedJannik Date: Mon, 16 Sep 2024 11:26:15 +0200 Subject: [PATCH 07/12] Adds missing Profile for Docker image --- basyx.gateway/basyx.gateway.component/pom.xml | 62 +++++++++++++++++++ 1 file changed, 62 insertions(+) diff --git a/basyx.gateway/basyx.gateway.component/pom.xml b/basyx.gateway/basyx.gateway.component/pom.xml index 8c2c1c219..b4e3dbd8c 100644 --- a/basyx.gateway/basyx.gateway.component/pom.xml +++ b/basyx.gateway/basyx.gateway.component/pom.xml @@ -67,4 +67,66 @@ spring-boot-starter-actuator + + + docker + + + docker.namespace + + + + + + io.fabric8 + docker-maven-plugin + + + + + + + ${docker.host.port}:${docker.container.port} + + + + ${docker.container.waitForEndpoint} + + + + + + + + + build-docker + + + push-docker + + + docker-compose-up + pre-integration-test + + start + + + + docker-compose-down + post-integration-test + + stop + + + + + + org.apache.maven.plugins + maven-failsafe-plugin + + + + + + \ No newline at end of file From 7df6c03b7f9b16f653decfd59b89a4dfd6d99b68 Mon Sep 17 00:00:00 2001 From: FriedJannik Date: Tue, 17 Sep 2024 08:39:31 +0200 Subject: [PATCH 08/12] Adds build Plugin --- basyx.gateway/basyx.gateway.component/pom.xml | 13 +++++++++++++ 1 file changed, 13 insertions(+) diff --git a/basyx.gateway/basyx.gateway.component/pom.xml b/basyx.gateway/basyx.gateway.component/pom.xml index b4e3dbd8c..07ac480ed 100644 --- a/basyx.gateway/basyx.gateway.component/pom.xml +++ b/basyx.gateway/basyx.gateway.component/pom.xml @@ -67,6 +67,19 @@ spring-boot-starter-actuator + + + + + org.springframework.boot + spring-boot-maven-plugin + + exec + + + + + docker From 96383b63f003b164f3104004d543b78cbe92441a Mon Sep 17 00:00:00 2001 From: FriedJannik Date: Thu, 19 Sep 2024 15:51:11 +0200 Subject: [PATCH 09/12] Adds Registry-Integration for /shells Endpoint --- basyx.gateway/basyx.gateway-core/pom.xml | 9 +++ .../basyx/gateway/core/DefaultGateway.java | 61 +++++++++++++++- .../basyx/gateway/TestGateway.java | 72 +++++++++---------- basyx.gateway/basyx.gateway-http/pom.xml | 4 ++ .../basyx/gateway/http/GatewayHTTPApi.java | 2 +- .../http/GatewayHTTPApiController.java | 16 ++++- basyx.gateway/basyx.gateway.component/pom.xml | 4 ++ .../gateway/component/ConfigurationGuard.java | 7 +- .../GatewayComponentConfiguration.java | 40 +++++++++++ .../src/main/resources/application.properties | 8 +-- examples/BaSyxSecured/docker-compose.yaml | 2 +- 11 files changed, 176 insertions(+), 49 deletions(-) create mode 100644 basyx.gateway/basyx.gateway.component/src/main/java/org/eclipse/digitaltwin/basyx/gateway/component/GatewayComponentConfiguration.java diff --git a/basyx.gateway/basyx.gateway-core/pom.xml b/basyx.gateway/basyx.gateway-core/pom.xml index ad04353e5..2ac5d2c33 100644 --- a/basyx.gateway/basyx.gateway-core/pom.xml +++ b/basyx.gateway/basyx.gateway-core/pom.xml @@ -17,6 +17,15 @@ org.eclipse.digitaltwin.basyx basyx.core + + org.eclipse.digitaltwin.basyx + basyx.aasrepository-client + + + org.eclipse.digitaltwin.basyx + basyx.aasregistry-client-native + 2.0.0-milestone-04 + org.eclipse.digitaltwin.aas4j aas4j-model diff --git a/basyx.gateway/basyx.gateway-core/src/main/java/org/eclipse/digitaltwin/basyx/gateway/core/DefaultGateway.java b/basyx.gateway/basyx.gateway-core/src/main/java/org/eclipse/digitaltwin/basyx/gateway/core/DefaultGateway.java index d840c9fd6..6509ac667 100644 --- a/basyx.gateway/basyx.gateway-core/src/main/java/org/eclipse/digitaltwin/basyx/gateway/core/DefaultGateway.java +++ b/basyx.gateway/basyx.gateway-core/src/main/java/org/eclipse/digitaltwin/basyx/gateway/core/DefaultGateway.java @@ -25,15 +25,27 @@ package org.eclipse.digitaltwin.basyx.gateway.core; +import com.fasterxml.jackson.annotation.JsonInclude; +import com.fasterxml.jackson.databind.ObjectMapper; import org.eclipse.digitaltwin.aas4j.v3.model.AssetAdministrationShell; +import org.eclipse.digitaltwin.basyx.aasregistry.main.client.factory.AasDescriptorFactory; +import org.eclipse.digitaltwin.basyx.aasregistry.main.client.mapper.AttributeMapper; +import org.eclipse.digitaltwin.basyx.aasrepository.client.ConnectedAasRepository; +import org.eclipse.digitaltwin.basyx.core.exceptions.RepositoryRegistryLinkException; import org.eclipse.digitaltwin.basyx.gateway.core.exception.BaSyxComponentNotHealthyException; import org.eclipse.digitaltwin.basyx.gateway.core.feature.Gateway; +import org.eclipse.digitaltwin.basyx.http.Aas4JHTTPSerializationExtension; import org.slf4j.Logger; import org.slf4j.LoggerFactory; +import org.eclipse.digitaltwin.basyx.aasregistry.client.ApiException; +import org.eclipse.digitaltwin.basyx.aasregistry.client.api.RegistryAndDiscoveryInterfaceApi; +import org.eclipse.digitaltwin.basyx.aasregistry.client.model.AssetAdministrationShellDescriptor; +import org.springframework.http.converter.json.Jackson2ObjectMapperBuilder; import java.io.IOException; import java.net.HttpURLConnection; import java.net.URL; +import java.util.List; public class DefaultGateway implements Gateway { @@ -41,10 +53,28 @@ public class DefaultGateway implements Gateway { @Override public void createAAS(AssetAdministrationShell aas, String aasRepository, String aasRegistry) throws BaSyxComponentNotHealthyException{ + System.out.println("Creating AAS "+aas.getId()+" in AAS Repository "+aasRepository+" and linking it with AAS Registry "+aasRegistry); + if(aasRepository == null) { + throw new UnsupportedOperationException("No AAS Repository configured"); + } throwExceptionIfIsUnhealthyBaSyxComponent(aasRepository); if(aasRegistry != null){ throwExceptionIfIsUnhealthyBaSyxComponent(aasRegistry); } + + ConnectedAasRepository aasRepo = new ConnectedAasRepository(aasRepository); + aasRepo.createAas(aas); + + if(aasRegistry != null){ + try { + integrateAasWithRegistry(aas, aasRegistry, aasRepository); + }catch(RepositoryRegistryLinkException e){ + logger.error("Unable to link AAS "+aas.getId()+" with registry "+aasRegistry+". Rolling back..."); + aasRepo.deleteAas(aas.getId()); + logger.error("Rollback in AAS Repository "+aasRepository+" completed."); + throw new BaSyxComponentNotHealthyException("Unable to link AAS with registry. Changes in AAS Repository rolled back."); + } + } } private void throwExceptionIfIsUnhealthyBaSyxComponent(String componentURL) { @@ -75,7 +105,7 @@ private boolean isBaSyxComponent(String componentURL) { try { HttpURLConnection connection = getRequest(componentURL, "/shells"); - String aasMiddleware = connection.getHeaderField("AASMiddleware"); + String aasMiddleware = connection.getHeaderField("aas_middleware"); return "BaSyx".equals(aasMiddleware); } catch (Exception e) { @@ -90,7 +120,7 @@ private boolean isHealthy(String componentURL){ String body = new String(connection.getInputStream().readAllBytes()); - return connection.getResponseCode() == 200 && body.equals("{'status':'UP'}"); + return connection.getResponseCode() == 200 && body.contains("UP"); } catch (Exception e) { logger.error(e.getMessage()); @@ -105,4 +135,31 @@ private static HttpURLConnection getRequest(String componentURL, String path) th connection.connect(); return connection; } + + private void integrateAasWithRegistry(AssetAdministrationShell shell,String aasRegistryUrl, String aasRepositoryUrl) throws RepositoryRegistryLinkException{ + List aasRepositoryURLs = List.of(aasRepositoryUrl); + AttributeMapper attributeMapper = getAttributeMapper(); + + AssetAdministrationShellDescriptor descriptor = new AasDescriptorFactory(shell, aasRepositoryURLs, attributeMapper).create(); + + RegistryAndDiscoveryInterfaceApi registryApi = new RegistryAndDiscoveryInterfaceApi(aasRegistryUrl); + + try { + registryApi.postAssetAdministrationShellDescriptor(descriptor); + + } catch (ApiException e) { + throw new RepositoryRegistryLinkException(shell.getId(), e); + } + logger.info("Shell '{}' has been automatically linked with the Registry", shell.getId()); + + } + + private static AttributeMapper getAttributeMapper() { + Jackson2ObjectMapperBuilder builder = new Jackson2ObjectMapperBuilder().serializationInclusion(JsonInclude.Include.NON_NULL); + Aas4JHTTPSerializationExtension extension = new Aas4JHTTPSerializationExtension(); + extension.extend(builder); + ObjectMapper objectMapper = builder.build(); + AttributeMapper attributeMapper = new AttributeMapper(objectMapper); + return attributeMapper; + } } diff --git a/basyx.gateway/basyx.gateway-core/src/test/java/org/eclipse/digitaltwin/basyx/gateway/TestGateway.java b/basyx.gateway/basyx.gateway-core/src/test/java/org/eclipse/digitaltwin/basyx/gateway/TestGateway.java index e0d23a0a3..ad58b81df 100644 --- a/basyx.gateway/basyx.gateway-core/src/test/java/org/eclipse/digitaltwin/basyx/gateway/TestGateway.java +++ b/basyx.gateway/basyx.gateway-core/src/test/java/org/eclipse/digitaltwin/basyx/gateway/TestGateway.java @@ -26,6 +26,9 @@ package org.eclipse.digitaltwin.basyx.gateway; import org.apache.http.HttpStatus; +import org.eclipse.digitaltwin.aas4j.v3.model.AssetAdministrationShell; +import org.eclipse.digitaltwin.aas4j.v3.model.impl.DefaultAssetAdministrationShell; +import org.eclipse.digitaltwin.aas4j.v3.model.impl.DefaultAssetInformation; import org.eclipse.digitaltwin.basyx.gateway.core.DefaultGateway; import org.eclipse.digitaltwin.basyx.gateway.core.exception.BaSyxComponentNotHealthyException; import org.eclipse.digitaltwin.basyx.gateway.core.feature.Gateway; @@ -35,6 +38,7 @@ import org.junit.Test; import org.mockserver.client.MockServerClient; import org.mockserver.integration.ClientAndServer; +import org.mockserver.model.HttpResponse; import org.mockserver.verify.VerificationTimes; public class TestGateway { @@ -56,65 +60,53 @@ public void tearDown(){ @Test public void testCreateAASWithHealthyBaSyxComponent(){ - new MockServerClient("localhost", 6006) - .when(org.mockserver.model.HttpRequest.request() - .withMethod("GET") - .withPath("/shells")) - .respond(org.mockserver.model.HttpResponse.response() - .withStatusCode(HttpStatus.SC_OK) - .withHeader("AASMiddleware","BaSyx") - .withBody("{}")); - new MockServerClient("localhost", 6006) - .when(org.mockserver.model.HttpRequest.request() - .withMethod("GET") - .withPath("/actuator/health")) - .respond(org.mockserver.model.HttpResponse.response() - .withStatusCode(HttpStatus.SC_OK) - .withBody("{'status':'UP'}")); - gateway.createAAS(null, "http://localhost:6006", null); + getExpectations(HttpResponse.response() + .withStatusCode(HttpStatus.SC_OK) + .withHeader("AASMiddleware", "BaSyx"), "{'status':'UP'}"); + gateway.createAAS(getDummyShell(), "http://localhost:6006", null); verifyCall("/shells",1); verifyCall("/actuator/health",1); } @Test(expected = BaSyxComponentNotHealthyException.class) public void testCreateAASWithUnhealthyBaSyxComponent(){ - new MockServerClient("localhost", 6006) - .when(org.mockserver.model.HttpRequest.request() - .withMethod("GET") - .withPath("/shells")) - .respond(org.mockserver.model.HttpResponse.response() - .withStatusCode(HttpStatus.SC_OK) - .withHeader("AASMiddleware","BaSyx") - .withBody("{}")); - new MockServerClient("localhost", 6006) - .when(org.mockserver.model.HttpRequest.request() - .withMethod("GET") - .withPath("/actuator/health")) - .respond(org.mockserver.model.HttpResponse.response() - .withStatusCode(HttpStatus.SC_OK) - .withBody("{'status':'DOWN'}")); - gateway.createAAS(null, "http://localhost:6006", null); + getExpectations(HttpResponse.response() + .withStatusCode(HttpStatus.SC_OK) + .withHeader("AASMiddleware", "BaSyx"), "{'status':'DOWN'}"); + gateway.createAAS(getDummyShell(), "http://localhost:6006", null); } @Test public void testCreateAASWithNonBaSyxComponent(){ + getExpectations(org.mockserver.model.HttpResponse.response() + .withStatusCode(HttpStatus.SC_OK), "{'status':'UP'}"); + gateway.createAAS(getDummyShell(), "http://localhost:6006", null); + verifyCall("/shells",1); + verifyCall("/actuator/health",0); + } + + private static void getExpectations(HttpResponse SC_OK, String responseBody) { new MockServerClient("localhost", 6006) .when(org.mockserver.model.HttpRequest.request() .withMethod("GET") .withPath("/shells")) - .respond(org.mockserver.model.HttpResponse.response() + .respond(SC_OK + .withBody("{}")); + new MockServerClient("localhost", 6006) + .when(org.mockserver.model.HttpRequest.request() + .withMethod("POST") + .withPath("/shells")) + .respond(HttpResponse.response() .withStatusCode(HttpStatus.SC_OK) + .withHeader("AASMiddleware", "BaSyx") .withBody("{}")); new MockServerClient("localhost", 6006) .when(org.mockserver.model.HttpRequest.request() .withMethod("GET") .withPath("/actuator/health")) - .respond(org.mockserver.model.HttpResponse.response() + .respond(HttpResponse.response() .withStatusCode(HttpStatus.SC_OK) - .withBody("{'status':'UP'}")); - gateway.createAAS(null, "http://localhost:6006", null); - verifyCall("/shells",1); - verifyCall("/actuator/health",0); + .withBody(responseBody)); } private void verifyCall(String path, int timesCalled) { @@ -122,4 +114,8 @@ private void verifyCall(String path, int timesCalled) { .withPath(path) , VerificationTimes.exactly(timesCalled)); } + + private AssetAdministrationShell getDummyShell(){ + return new DefaultAssetAdministrationShell.Builder().id("TestId").idShort("test").assetInformation(new DefaultAssetInformation.Builder().build()).build(); + } } diff --git a/basyx.gateway/basyx.gateway-http/pom.xml b/basyx.gateway/basyx.gateway-http/pom.xml index e64eff3be..0fb1df80b 100644 --- a/basyx.gateway/basyx.gateway-http/pom.xml +++ b/basyx.gateway/basyx.gateway-http/pom.xml @@ -14,6 +14,10 @@ BaSyx Gateway HTTP + + org.eclipse.digitaltwin.basyx + basyx.gateway-core + org.eclipse.digitaltwin.basyx basyx.http diff --git a/basyx.gateway/basyx.gateway-http/src/main/java/org/eclipse/digitaltwin/basyx/gateway/http/GatewayHTTPApi.java b/basyx.gateway/basyx.gateway-http/src/main/java/org/eclipse/digitaltwin/basyx/gateway/http/GatewayHTTPApi.java index 3854896bd..32eaa767c 100644 --- a/basyx.gateway/basyx.gateway-http/src/main/java/org/eclipse/digitaltwin/basyx/gateway/http/GatewayHTTPApi.java +++ b/basyx.gateway/basyx.gateway-http/src/main/java/org/eclipse/digitaltwin/basyx/gateway/http/GatewayHTTPApi.java @@ -61,7 +61,7 @@ public interface GatewayHTTPApi { ResponseEntity postAssetAdministrationShell( @Parameter(in = ParameterIn.DEFAULT, description = "Asset Administration Shell object", required = true, schema = @Schema()) @Valid @RequestBody AssetAdministrationShell body, @Parameter(in = ParameterIn.QUERY, description = "The Asset Administration Shell Repository URL", schema = @Schema()) @RequestParam(value = "aasRepositoryURL", required = false) String aasRepositoryURL, - @Parameter(in = ParameterIn.QUERY, description = "The Asset Administration Shell Repository URL", schema = @Schema()) @RequestParam(value = "aasRegistryURL", required = false) String aasRegistryURL + @Parameter(in = ParameterIn.QUERY, description = "The Asset Administration Shell Registry URL", schema = @Schema()) @RequestParam(value = "aasRegistryURL", required = false) String aasRegistryURL ); diff --git a/basyx.gateway/basyx.gateway-http/src/main/java/org/eclipse/digitaltwin/basyx/gateway/http/GatewayHTTPApiController.java b/basyx.gateway/basyx.gateway-http/src/main/java/org/eclipse/digitaltwin/basyx/gateway/http/GatewayHTTPApiController.java index 2587c51fa..c54bca750 100644 --- a/basyx.gateway/basyx.gateway-http/src/main/java/org/eclipse/digitaltwin/basyx/gateway/http/GatewayHTTPApiController.java +++ b/basyx.gateway/basyx.gateway-http/src/main/java/org/eclipse/digitaltwin/basyx/gateway/http/GatewayHTTPApiController.java @@ -26,6 +26,8 @@ package org.eclipse.digitaltwin.basyx.gateway.http; import org.eclipse.digitaltwin.aas4j.v3.model.AssetAdministrationShell; +import org.eclipse.digitaltwin.basyx.gateway.core.exception.BaSyxComponentNotHealthyException; +import org.eclipse.digitaltwin.basyx.gateway.core.feature.Gateway; import org.springframework.beans.factory.annotation.Value; import org.springframework.boot.autoconfigure.condition.ConditionalOnProperty; import org.springframework.http.HttpStatus; @@ -44,14 +46,24 @@ public class GatewayHTTPApiController implements GatewayHTTPApi{ @Value("${basyx.gateway.submodel-registry:#{null}}") public String submodelRegistryURL; + private final Gateway gateway; + + public GatewayHTTPApiController(Gateway gateway){ + this.gateway = gateway; + } + @Override public ResponseEntity postAssetAdministrationShell(AssetAdministrationShell body, String aasRepositoryURL, String aasRegistryURL) { if(!isAnyURLSet(aasRepositoryURL, this.aasRepositoryURL)){ return ResponseEntity.badRequest().body("No AAS Repository URL set"); - } + try { + gateway.createAAS(body, aasRepositoryURL == null ? this.aasRepositoryURL : aasRepositoryURL, aasRegistryURL); + }catch(BaSyxComponentNotHealthyException e){ + return ResponseEntity.status(HttpStatus.BAD_REQUEST).body(e.getMessage()); - return null; + } + return new ResponseEntity(body, HttpStatus.CREATED); } private static boolean isAnyURLSet(String url1, String url2) { diff --git a/basyx.gateway/basyx.gateway.component/pom.xml b/basyx.gateway/basyx.gateway.component/pom.xml index 07ac480ed..7eecfef9b 100644 --- a/basyx.gateway/basyx.gateway.component/pom.xml +++ b/basyx.gateway/basyx.gateway.component/pom.xml @@ -18,6 +18,10 @@ + + org.eclipse.digitaltwin.basyx + basyx.aasrepository-client + org.eclipse.digitaltwin.basyx basyx.gateway-core diff --git a/basyx.gateway/basyx.gateway.component/src/main/java/org/eclipse/digitaltwin/basyx/gateway/component/ConfigurationGuard.java b/basyx.gateway/basyx.gateway.component/src/main/java/org/eclipse/digitaltwin/basyx/gateway/component/ConfigurationGuard.java index 8d9e9cdc3..c458e9564 100644 --- a/basyx.gateway/basyx.gateway.component/src/main/java/org/eclipse/digitaltwin/basyx/gateway/component/ConfigurationGuard.java +++ b/basyx.gateway/basyx.gateway.component/src/main/java/org/eclipse/digitaltwin/basyx/gateway/component/ConfigurationGuard.java @@ -35,6 +35,11 @@ import java.util.List; import java.util.stream.Collectors; +/** + * Class that prints error and warning messages to inform the user about possible misconfiguration + * + * @author fried + */ @Component public class ConfigurationGuard implements InitializingBean { private static final Logger logger = LoggerFactory.getLogger(ConfigurationGuard.class); @@ -55,7 +60,7 @@ public void afterPropertiesSet() throws Exception { printWarning(missingNonRequiredProperties); } - logger.info("\n:::::::::::::::: BaSyx Gateway Configuration ::::::::::::::::"); + logger.info(":::::::::::::::: BaSyx Gateway Configuration ::::::::::::::::"); if (aasRepositoryURL != null) { logger.info(":: Default AAS Repository URL: " + aasRepositoryURL); } diff --git a/basyx.gateway/basyx.gateway.component/src/main/java/org/eclipse/digitaltwin/basyx/gateway/component/GatewayComponentConfiguration.java b/basyx.gateway/basyx.gateway.component/src/main/java/org/eclipse/digitaltwin/basyx/gateway/component/GatewayComponentConfiguration.java new file mode 100644 index 000000000..95e9ed656 --- /dev/null +++ b/basyx.gateway/basyx.gateway.component/src/main/java/org/eclipse/digitaltwin/basyx/gateway/component/GatewayComponentConfiguration.java @@ -0,0 +1,40 @@ +/******************************************************************************* + * Copyright (C) 2024 the Eclipse BaSyx Authors + * + * Permission is hereby granted, free of charge, to any person obtaining + * a copy of this software and associated documentation files (the + * "Software"), to deal in the Software without restriction, including + * without limitation the rights to use, copy, modify, merge, publish, + * distribute, sublicense, and/or sell copies of the Software, and to + * permit persons to whom the Software is furnished to do so, subject to + * the following conditions: + * + * The above copyright notice and this permission notice shall be + * included in all copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, + * EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF + * MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND + * NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE + * LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION + * OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION + * WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. + * + * SPDX-License-Identifier: MIT + ******************************************************************************/ + +package org.eclipse.digitaltwin.basyx.gateway.component; + +import org.eclipse.digitaltwin.basyx.gateway.core.DefaultGateway; +import org.eclipse.digitaltwin.basyx.gateway.core.feature.Gateway; +import org.springframework.context.annotation.Bean; +import org.springframework.context.annotation.Configuration; + +@Configuration +public class GatewayComponentConfiguration { + + @Bean + public Gateway getGateway() { + return new DefaultGateway(); + } +} diff --git a/basyx.gateway/basyx.gateway.component/src/main/resources/application.properties b/basyx.gateway/basyx.gateway.component/src/main/resources/application.properties index 2e191e7ee..b3934437f 100644 --- a/basyx.gateway/basyx.gateway.component/src/main/resources/application.properties +++ b/basyx.gateway/basyx.gateway.component/src/main/resources/application.properties @@ -2,13 +2,13 @@ server.port=5999 ####################################################### -# AAS/Submodel Repository URL (optional, recommended) # +# Default AAS/Submodel Repository URL (optional, recommended) # ####################################################### basyx.gateway.aas-repository=http://localhost:8081 -basyx.gateway.submodel-repository=http://localhost:8082 +#basyx.gateway.submodel-repository=http://localhost:8082 ############################################################# # Default AAS/Submodel Registry URL (optional, recommended) # ############################################################# -basyx.gateway.aas-registry=http://localhost:8083 -basyx.gateway.submodel-registry=http://localhost:8084 +#basyx.gateway.aas-registry=http://localhost:8083 +#basyx.gateway.submodel-registry=http://localhost:8084 diff --git a/examples/BaSyxSecured/docker-compose.yaml b/examples/BaSyxSecured/docker-compose.yaml index 458a71e35..a1297f083 100644 --- a/examples/BaSyxSecured/docker-compose.yaml +++ b/examples/BaSyxSecured/docker-compose.yaml @@ -75,7 +75,7 @@ services: # AAS Web UI aas-web-ui: - image: eclipsebasyx/aas-gui:v2-240801 + image: eclipsebasyx/aas-gui:v2-240913 container_name: aas-ui extra_hosts: - "keycloak:127.0.0.1" From 6748b395c61c38c037e488a7581ebeada196944f Mon Sep 17 00:00:00 2001 From: FriedJannik Date: Thu, 19 Sep 2024 16:18:46 +0200 Subject: [PATCH 10/12] Adapts Tests --- .../basyx/gateway/core/DefaultGateway.java | 33 ++++++-- .../RegistryUnavailableException.java | 33 ++++++++ .../basyx/gateway/TestGateway.java | 82 +++++++++++++++---- 3 files changed, 127 insertions(+), 21 deletions(-) create mode 100644 basyx.gateway/basyx.gateway-core/src/main/java/org/eclipse/digitaltwin/basyx/gateway/core/exception/RegistryUnavailableException.java diff --git a/basyx.gateway/basyx.gateway-core/src/main/java/org/eclipse/digitaltwin/basyx/gateway/core/DefaultGateway.java b/basyx.gateway/basyx.gateway-core/src/main/java/org/eclipse/digitaltwin/basyx/gateway/core/DefaultGateway.java index 6509ac667..51d2586fe 100644 --- a/basyx.gateway/basyx.gateway-core/src/main/java/org/eclipse/digitaltwin/basyx/gateway/core/DefaultGateway.java +++ b/basyx.gateway/basyx.gateway-core/src/main/java/org/eclipse/digitaltwin/basyx/gateway/core/DefaultGateway.java @@ -33,6 +33,7 @@ import org.eclipse.digitaltwin.basyx.aasrepository.client.ConnectedAasRepository; import org.eclipse.digitaltwin.basyx.core.exceptions.RepositoryRegistryLinkException; import org.eclipse.digitaltwin.basyx.gateway.core.exception.BaSyxComponentNotHealthyException; +import org.eclipse.digitaltwin.basyx.gateway.core.exception.RegistryUnavailableException; import org.eclipse.digitaltwin.basyx.gateway.core.feature.Gateway; import org.eclipse.digitaltwin.basyx.http.Aas4JHTTPSerializationExtension; import org.slf4j.Logger; @@ -57,9 +58,9 @@ public void createAAS(AssetAdministrationShell aas, String aasRepository, String if(aasRepository == null) { throw new UnsupportedOperationException("No AAS Repository configured"); } - throwExceptionIfIsUnhealthyBaSyxComponent(aasRepository); + throwExceptionIfIsUnhealthyBaSyxRepository(aasRepository); if(aasRegistry != null){ - throwExceptionIfIsUnhealthyBaSyxComponent(aasRegistry); + throwExceptionIfIsUnhealthyBaSyxRegistry(aasRegistry); } ConnectedAasRepository aasRepo = new ConnectedAasRepository(aasRepository); @@ -72,15 +73,23 @@ public void createAAS(AssetAdministrationShell aas, String aasRepository, String logger.error("Unable to link AAS "+aas.getId()+" with registry "+aasRegistry+". Rolling back..."); aasRepo.deleteAas(aas.getId()); logger.error("Rollback in AAS Repository "+aasRepository+" completed."); - throw new BaSyxComponentNotHealthyException("Unable to link AAS with registry. Changes in AAS Repository rolled back."); + throw new RegistryUnavailableException("Unable to link AAS with registry. Changes in AAS Repository rolled back."); } } } - private void throwExceptionIfIsUnhealthyBaSyxComponent(String componentURL) { + private void throwExceptionIfIsUnhealthyBaSyxRepository(String componentURL) { componentURL = formatURL(componentURL); - if (isBaSyxComponent(componentURL) && !isHealthy(componentURL)) { + if (isBaSyxRepository(componentURL) && !isHealthy(componentURL)) { + throw new BaSyxComponentNotHealthyException(componentURL + " is not healthy"); + } + } + + private void throwExceptionIfIsUnhealthyBaSyxRegistry(String componentURL) { + componentURL = formatURL(componentURL); + + if (isBaSyxRegistry(componentURL) && !isHealthy(componentURL)) { throw new BaSyxComponentNotHealthyException(componentURL + " is not healthy"); } } @@ -101,7 +110,7 @@ private String formatURL(String componentURL) { return componentURL; } - private boolean isBaSyxComponent(String componentURL) { + private boolean isBaSyxRepository(String componentURL) { try { HttpURLConnection connection = getRequest(componentURL, "/shells"); @@ -113,6 +122,18 @@ private boolean isBaSyxComponent(String componentURL) { return false; } } + private boolean isBaSyxRegistry(String componentURL) { + try { + HttpURLConnection connection = getRequest(componentURL, "/shell-descriptors"); + + String aasMiddleware = connection.getHeaderField("aas_middleware"); + + return "BaSyx".equals(aasMiddleware); + } catch (Exception e) { + logger.error(e.getMessage()); + return false; + } + } private boolean isHealthy(String componentURL){ try { diff --git a/basyx.gateway/basyx.gateway-core/src/main/java/org/eclipse/digitaltwin/basyx/gateway/core/exception/RegistryUnavailableException.java b/basyx.gateway/basyx.gateway-core/src/main/java/org/eclipse/digitaltwin/basyx/gateway/core/exception/RegistryUnavailableException.java new file mode 100644 index 000000000..3648e7c49 --- /dev/null +++ b/basyx.gateway/basyx.gateway-core/src/main/java/org/eclipse/digitaltwin/basyx/gateway/core/exception/RegistryUnavailableException.java @@ -0,0 +1,33 @@ +/******************************************************************************* + * Copyright (C) 2024 the Eclipse BaSyx Authors + * + * Permission is hereby granted, free of charge, to any person obtaining + * a copy of this software and associated documentation files (the + * "Software"), to deal in the Software without restriction, including + * without limitation the rights to use, copy, modify, merge, publish, + * distribute, sublicense, and/or sell copies of the Software, and to + * permit persons to whom the Software is furnished to do so, subject to + * the following conditions: + * + * The above copyright notice and this permission notice shall be + * included in all copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, + * EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF + * MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND + * NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE + * LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION + * OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION + * WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. + * + * SPDX-License-Identifier: MIT + ******************************************************************************/ + +package org.eclipse.digitaltwin.basyx.gateway.core.exception; + +public class RegistryUnavailableException extends RuntimeException{ + + public RegistryUnavailableException(String msg){ + super(msg); + } +} diff --git a/basyx.gateway/basyx.gateway-core/src/test/java/org/eclipse/digitaltwin/basyx/gateway/TestGateway.java b/basyx.gateway/basyx.gateway-core/src/test/java/org/eclipse/digitaltwin/basyx/gateway/TestGateway.java index ad58b81df..81f5acf5b 100644 --- a/basyx.gateway/basyx.gateway-core/src/test/java/org/eclipse/digitaltwin/basyx/gateway/TestGateway.java +++ b/basyx.gateway/basyx.gateway-core/src/test/java/org/eclipse/digitaltwin/basyx/gateway/TestGateway.java @@ -31,10 +31,11 @@ import org.eclipse.digitaltwin.aas4j.v3.model.impl.DefaultAssetInformation; import org.eclipse.digitaltwin.basyx.gateway.core.DefaultGateway; import org.eclipse.digitaltwin.basyx.gateway.core.exception.BaSyxComponentNotHealthyException; +import org.eclipse.digitaltwin.basyx.gateway.core.exception.RegistryUnavailableException; import org.eclipse.digitaltwin.basyx.gateway.core.feature.Gateway; +import org.eclipse.digitaltwin.basyx.http.Base64UrlEncodedIdentifier; import org.junit.After; import org.junit.Before; -import org.junit.BeforeClass; import org.junit.Test; import org.mockserver.client.MockServerClient; import org.mockserver.integration.ClientAndServer; @@ -43,6 +44,8 @@ public class TestGateway { + public static final String AAS_REPOSITORY_URL = "http://localhost:6006"; + public static final String AAS_REGISTRY_URL = "http://localhost:7007"; public static ClientAndServer mockBaSyxAasRepository; public static ClientAndServer mockAasRegistry; private Gateway gateway; @@ -50,39 +53,58 @@ public class TestGateway { @Before public void setUp(){ mockBaSyxAasRepository = ClientAndServer.startClientAndServer(6006); + mockAasRegistry = ClientAndServer.startClientAndServer(7007); gateway = new DefaultGateway(); } @After public void tearDown(){ mockBaSyxAasRepository.stop(); + mockAasRegistry.stop(); } @Test public void testCreateAASWithHealthyBaSyxComponent(){ getExpectations(HttpResponse.response() .withStatusCode(HttpStatus.SC_OK) - .withHeader("AASMiddleware", "BaSyx"), "{'status':'UP'}"); - gateway.createAAS(getDummyShell(), "http://localhost:6006", null); - verifyCall("/shells",1); - verifyCall("/actuator/health",1); + .withHeader("aas_middleware", "BaSyx"), "{'status':'UP'}"); + gateway.createAAS(getDummyShell(), AAS_REPOSITORY_URL, null); + verifyGetCall("localhost",6006,"/shells",1); + verifyGetCall("localhost",6006,"/actuator/health",1); } @Test(expected = BaSyxComponentNotHealthyException.class) public void testCreateAASWithUnhealthyBaSyxComponent(){ getExpectations(HttpResponse.response() .withStatusCode(HttpStatus.SC_OK) - .withHeader("AASMiddleware", "BaSyx"), "{'status':'DOWN'}"); - gateway.createAAS(getDummyShell(), "http://localhost:6006", null); + .withHeader("aas_middleware", "BaSyx"), "{'status':'DOWN'}"); + gateway.createAAS(getDummyShell(), AAS_REPOSITORY_URL, null); } @Test public void testCreateAASWithNonBaSyxComponent(){ getExpectations(org.mockserver.model.HttpResponse.response() .withStatusCode(HttpStatus.SC_OK), "{'status':'UP'}"); - gateway.createAAS(getDummyShell(), "http://localhost:6006", null); - verifyCall("/shells",1); - verifyCall("/actuator/health",0); + gateway.createAAS(getDummyShell(), AAS_REPOSITORY_URL, null); + verifyGetCall("localhost",6006,"/shells",1); + verifyGetCall("localhost",6006,"/actuator/health",0); + } + @Test(expected = RegistryUnavailableException.class) + public void testCreateAASWithOfflineRegistry(){ + getExpectations(org.mockserver.model.HttpResponse.response() + .withStatusCode(HttpStatus.SC_OK), "{'status':'UP'}"); + gateway.createAAS(getDummyShell(), AAS_REPOSITORY_URL, "http://localhost:8008"); + verifyPostCall("localhost",6006,"/shells",1); + verifyDeleteCall("localhost",6006,"/shells/"+getEncodedShellIdentifer(),1); + } + @Test + public void testCreateAASWithRegistryIntegration(){ + getExpectations(org.mockserver.model.HttpResponse.response() + .withStatusCode(HttpStatus.SC_OK), "{'status':'UP'}"); + gateway.createAAS(getDummyShell(), AAS_REPOSITORY_URL, AAS_REGISTRY_URL); + verifyPostCall("localhost",6006,"/shells",1); + verifyPostCall("localhost",7007,"/shell-descriptors",1); + verifyDeleteCall("localhost",6006,"/shells/"+getEncodedShellIdentifer(),0); } private static void getExpectations(HttpResponse SC_OK, String responseBody) { @@ -98,7 +120,23 @@ private static void getExpectations(HttpResponse SC_OK, String responseBody) { .withPath("/shells")) .respond(HttpResponse.response() .withStatusCode(HttpStatus.SC_OK) - .withHeader("AASMiddleware", "BaSyx") + .withHeader("aas_middleware", "BaSyx") + .withBody("{}")); + new MockServerClient("localhost", 7007) + .when(org.mockserver.model.HttpRequest.request() + .withMethod("POST") + .withPath("/shell-descriptors")) + .respond(HttpResponse.response() + .withStatusCode(HttpStatus.SC_OK) + .withHeader("aas_middleware", "BaSyx") + .withBody("{}")); + new MockServerClient("localhost", 6006) + .when(org.mockserver.model.HttpRequest.request() + .withMethod("DELETE") + .withPath("/shells/"+getEncodedShellIdentifer())) + .respond(HttpResponse.response() + .withStatusCode(HttpStatus.SC_NO_CONTENT) + .withHeader("aas_middleware", "BaSyx") .withBody("{}")); new MockServerClient("localhost", 6006) .when(org.mockserver.model.HttpRequest.request() @@ -109,13 +147,27 @@ private static void getExpectations(HttpResponse SC_OK, String responseBody) { .withBody(responseBody)); } - private void verifyCall(String path, int timesCalled) { - new MockServerClient("localhost", 6006).verify(org.mockserver.model.HttpRequest.request().withMethod("GET") - .withPath(path) + private void verifyGetCall(String host, int port, String path, int timesCalled) { + new MockServerClient(host, port).verify(org.mockserver.model.HttpRequest.request().withMethod("GET") + .withPath(path) + , VerificationTimes.exactly(timesCalled)); + } + private void verifyPostCall(String host, int port, String path, int timesCalled) { + new MockServerClient(host, port).verify(org.mockserver.model.HttpRequest.request().withMethod("POST") + .withPath(path) + , VerificationTimes.exactly(timesCalled)); + } + private void verifyDeleteCall(String host, int port, String path, int timesCalled) { + new MockServerClient(host, port).verify(org.mockserver.model.HttpRequest.request().withMethod("DELETE") + .withPath(path) , VerificationTimes.exactly(timesCalled)); } - private AssetAdministrationShell getDummyShell(){ + private static AssetAdministrationShell getDummyShell(){ return new DefaultAssetAdministrationShell.Builder().id("TestId").idShort("test").assetInformation(new DefaultAssetInformation.Builder().build()).build(); } + + private static String getEncodedShellIdentifer(){ + return new Base64UrlEncodedIdentifier(getDummyShell().getId()).getEncodedIdentifier(); + } } From 70075a71f888a6c0037563c1e8a93a882dce3ebb Mon Sep 17 00:00:00 2001 From: FriedJannik Date: Thu, 19 Sep 2024 16:24:37 +0200 Subject: [PATCH 11/12] Refactors createAAS Method --- .../basyx/gateway/core/DefaultGateway.java | 51 ++++++++++++------- 1 file changed, 33 insertions(+), 18 deletions(-) diff --git a/basyx.gateway/basyx.gateway-core/src/main/java/org/eclipse/digitaltwin/basyx/gateway/core/DefaultGateway.java b/basyx.gateway/basyx.gateway-core/src/main/java/org/eclipse/digitaltwin/basyx/gateway/core/DefaultGateway.java index 51d2586fe..f9fb3abb5 100644 --- a/basyx.gateway/basyx.gateway-core/src/main/java/org/eclipse/digitaltwin/basyx/gateway/core/DefaultGateway.java +++ b/basyx.gateway/basyx.gateway-core/src/main/java/org/eclipse/digitaltwin/basyx/gateway/core/DefaultGateway.java @@ -53,31 +53,46 @@ public class DefaultGateway implements Gateway { private Logger logger = LoggerFactory.getLogger(DefaultGateway.class); @Override - public void createAAS(AssetAdministrationShell aas, String aasRepository, String aasRegistry) throws BaSyxComponentNotHealthyException{ - System.out.println("Creating AAS "+aas.getId()+" in AAS Repository "+aasRepository+" and linking it with AAS Registry "+aasRegistry); - if(aasRepository == null) { - throw new UnsupportedOperationException("No AAS Repository configured"); - } - throwExceptionIfIsUnhealthyBaSyxRepository(aasRepository); - if(aasRegistry != null){ - throwExceptionIfIsUnhealthyBaSyxRegistry(aasRegistry); - } + public void createAAS(AssetAdministrationShell aas, String aasRepository, String aasRegistry) throws BaSyxComponentNotHealthyException { + logger.info("Creating AAS {} in AAS Repository {} and linking it with AAS Registry {}", aas.getId(), aasRepository, aasRegistry); + + validateRepository(aasRepository); ConnectedAasRepository aasRepo = new ConnectedAasRepository(aasRepository); aasRepo.createAas(aas); - if(aasRegistry != null){ - try { - integrateAasWithRegistry(aas, aasRegistry, aasRepository); - }catch(RepositoryRegistryLinkException e){ - logger.error("Unable to link AAS "+aas.getId()+" with registry "+aasRegistry+". Rolling back..."); - aasRepo.deleteAas(aas.getId()); - logger.error("Rollback in AAS Repository "+aasRepository+" completed."); - throw new RegistryUnavailableException("Unable to link AAS with registry. Changes in AAS Repository rolled back."); - } + if (aasRegistry == null) { + return; + } + + validateRegistry(aasRegistry); + + try { + integrateAasWithRegistry(aas, aasRegistry, aasRepository); + } catch (RepositoryRegistryLinkException e) { + handleRegistryLinkException(aasRepo, aas, aasRepository, aasRegistry, e); } } + private void validateRepository(String aasRepository) throws BaSyxComponentNotHealthyException { + if (aasRepository == null) { + throw new UnsupportedOperationException("No AAS Repository configured"); + } + throwExceptionIfIsUnhealthyBaSyxRepository(aasRepository); + } + + private void validateRegistry(String aasRegistry) throws BaSyxComponentNotHealthyException { + throwExceptionIfIsUnhealthyBaSyxRegistry(aasRegistry); + } + + private void handleRegistryLinkException(ConnectedAasRepository aasRepo, AssetAdministrationShell aas, String aasRepository, String aasRegistry, RepositoryRegistryLinkException e) throws RegistryUnavailableException { + logger.error("Unable to link AAS {} with registry {}. Rolling back...", aas.getId(), aasRegistry); + aasRepo.deleteAas(aas.getId()); + logger.error("Rollback in AAS Repository {} completed.", aasRepository); + throw new RegistryUnavailableException("Unable to link AAS with registry. Changes in AAS Repository rolled back."); + } + + private void throwExceptionIfIsUnhealthyBaSyxRepository(String componentURL) { componentURL = formatURL(componentURL); From 98e41ff9f025c04ee151b6983e63b6a106178ded Mon Sep 17 00:00:00 2001 From: FriedJannik Date: Thu, 19 Sep 2024 16:45:43 +0200 Subject: [PATCH 12/12] Applies refactoring --- .../basyx/gateway/core/DefaultGateway.java | 165 ++---------------- .../basyx/gateway/core/Gateway.java | 7 +- .../basyx/gateway/core/GatewayFactory.java | 7 +- .../gateway/core/feature/GatewayFeature.java | 5 + .../AASRegistryIntegrator.java | 88 ++++++++++ .../SubmodelRegistryIntegrator.java | 62 +++++++ .../gateway/core/utils/GatewayUtils.java | 134 ++++++++++++++ .../basyx/gateway/TestGateway.java | 2 +- .../http/GatewayHTTPApiController.java | 2 +- .../GatewayComponentConfiguration.java | 2 +- 10 files changed, 318 insertions(+), 156 deletions(-) create mode 100644 basyx.gateway/basyx.gateway-core/src/main/java/org/eclipse/digitaltwin/basyx/gateway/core/registryintegration/AASRegistryIntegrator.java create mode 100644 basyx.gateway/basyx.gateway-core/src/main/java/org/eclipse/digitaltwin/basyx/gateway/core/registryintegration/SubmodelRegistryIntegrator.java create mode 100644 basyx.gateway/basyx.gateway-core/src/main/java/org/eclipse/digitaltwin/basyx/gateway/core/utils/GatewayUtils.java diff --git a/basyx.gateway/basyx.gateway-core/src/main/java/org/eclipse/digitaltwin/basyx/gateway/core/DefaultGateway.java b/basyx.gateway/basyx.gateway-core/src/main/java/org/eclipse/digitaltwin/basyx/gateway/core/DefaultGateway.java index f9fb3abb5..05fd5c843 100644 --- a/basyx.gateway/basyx.gateway-core/src/main/java/org/eclipse/digitaltwin/basyx/gateway/core/DefaultGateway.java +++ b/basyx.gateway/basyx.gateway-core/src/main/java/org/eclipse/digitaltwin/basyx/gateway/core/DefaultGateway.java @@ -25,177 +25,40 @@ package org.eclipse.digitaltwin.basyx.gateway.core; -import com.fasterxml.jackson.annotation.JsonInclude; -import com.fasterxml.jackson.databind.ObjectMapper; import org.eclipse.digitaltwin.aas4j.v3.model.AssetAdministrationShell; -import org.eclipse.digitaltwin.basyx.aasregistry.main.client.factory.AasDescriptorFactory; -import org.eclipse.digitaltwin.basyx.aasregistry.main.client.mapper.AttributeMapper; import org.eclipse.digitaltwin.basyx.aasrepository.client.ConnectedAasRepository; import org.eclipse.digitaltwin.basyx.core.exceptions.RepositoryRegistryLinkException; import org.eclipse.digitaltwin.basyx.gateway.core.exception.BaSyxComponentNotHealthyException; -import org.eclipse.digitaltwin.basyx.gateway.core.exception.RegistryUnavailableException; -import org.eclipse.digitaltwin.basyx.gateway.core.feature.Gateway; -import org.eclipse.digitaltwin.basyx.http.Aas4JHTTPSerializationExtension; -import org.slf4j.Logger; -import org.slf4j.LoggerFactory; -import org.eclipse.digitaltwin.basyx.aasregistry.client.ApiException; -import org.eclipse.digitaltwin.basyx.aasregistry.client.api.RegistryAndDiscoveryInterfaceApi; -import org.eclipse.digitaltwin.basyx.aasregistry.client.model.AssetAdministrationShellDescriptor; -import org.springframework.http.converter.json.Jackson2ObjectMapperBuilder; - -import java.io.IOException; -import java.net.HttpURLConnection; -import java.net.URL; -import java.util.List; +import org.eclipse.digitaltwin.basyx.gateway.core.registryintegration.AASRegistryIntegrator; +import org.eclipse.digitaltwin.basyx.gateway.core.utils.GatewayUtils; +/** + * Default implementation of the Gateway interface + * + * @author fried + */ public class DefaultGateway implements Gateway { - private Logger logger = LoggerFactory.getLogger(DefaultGateway.class); + private final GatewayUtils utils = new GatewayUtils(); @Override public void createAAS(AssetAdministrationShell aas, String aasRepository, String aasRegistry) throws BaSyxComponentNotHealthyException { - logger.info("Creating AAS {} in AAS Repository {} and linking it with AAS Registry {}", aas.getId(), aasRepository, aasRegistry); - - validateRepository(aasRepository); + utils.validateRepository(aasRepository); ConnectedAasRepository aasRepo = new ConnectedAasRepository(aasRepository); aasRepo.createAas(aas); - if (aasRegistry == null) { + if (!GatewayUtils.isRegistryDefined(aasRegistry)) { return; } - validateRegistry(aasRegistry); + utils.validateRegistry(aasRegistry); try { - integrateAasWithRegistry(aas, aasRegistry, aasRepository); + AASRegistryIntegrator integrator = new AASRegistryIntegrator(aasRegistry); + integrator.registerAAS(aas, aasRepository); } catch (RepositoryRegistryLinkException e) { - handleRegistryLinkException(aasRepo, aas, aasRepository, aasRegistry, e); - } - } - - private void validateRepository(String aasRepository) throws BaSyxComponentNotHealthyException { - if (aasRepository == null) { - throw new UnsupportedOperationException("No AAS Repository configured"); - } - throwExceptionIfIsUnhealthyBaSyxRepository(aasRepository); - } - - private void validateRegistry(String aasRegistry) throws BaSyxComponentNotHealthyException { - throwExceptionIfIsUnhealthyBaSyxRegistry(aasRegistry); - } - - private void handleRegistryLinkException(ConnectedAasRepository aasRepo, AssetAdministrationShell aas, String aasRepository, String aasRegistry, RepositoryRegistryLinkException e) throws RegistryUnavailableException { - logger.error("Unable to link AAS {} with registry {}. Rolling back...", aas.getId(), aasRegistry); - aasRepo.deleteAas(aas.getId()); - logger.error("Rollback in AAS Repository {} completed.", aasRepository); - throw new RegistryUnavailableException("Unable to link AAS with registry. Changes in AAS Repository rolled back."); - } - - - private void throwExceptionIfIsUnhealthyBaSyxRepository(String componentURL) { - componentURL = formatURL(componentURL); - - if (isBaSyxRepository(componentURL) && !isHealthy(componentURL)) { - throw new BaSyxComponentNotHealthyException(componentURL + " is not healthy"); - } - } - - private void throwExceptionIfIsUnhealthyBaSyxRegistry(String componentURL) { - componentURL = formatURL(componentURL); - - if (isBaSyxRegistry(componentURL) && !isHealthy(componentURL)) { - throw new BaSyxComponentNotHealthyException(componentURL + " is not healthy"); - } - } - - private String formatURL(String componentURL) { - try { - URL url = new URL(componentURL); - String protocol = url.getProtocol(); - String host = url.getHost(); - int port = url.getPort(); - if (port == -1) { // no port specified, use default port - port = url.getDefaultPort(); - } - componentURL = protocol + "://" + host + ":" + port; - } catch (Exception e) { - logger.error(e.getMessage()); - } - return componentURL; - } - - private boolean isBaSyxRepository(String componentURL) { - try { - HttpURLConnection connection = getRequest(componentURL, "/shells"); - - String aasMiddleware = connection.getHeaderField("aas_middleware"); - - return "BaSyx".equals(aasMiddleware); - } catch (Exception e) { - logger.error(e.getMessage()); - return false; - } - } - private boolean isBaSyxRegistry(String componentURL) { - try { - HttpURLConnection connection = getRequest(componentURL, "/shell-descriptors"); - - String aasMiddleware = connection.getHeaderField("aas_middleware"); - - return "BaSyx".equals(aasMiddleware); - } catch (Exception e) { - logger.error(e.getMessage()); - return false; - } - } - - private boolean isHealthy(String componentURL){ - try { - HttpURLConnection connection = getRequest(componentURL, "/actuator/health"); - - String body = new String(connection.getInputStream().readAllBytes()); - - return connection.getResponseCode() == 200 && body.contains("UP"); - - } catch (Exception e) { - logger.error(e.getMessage()); - return false; + utils.handleRegistryLinkException(aasRepo, aas, aasRepository, aasRegistry, e); } } - - private static HttpURLConnection getRequest(String componentURL, String path) throws IOException { - URL url = new URL(componentURL + path); - HttpURLConnection connection = (HttpURLConnection) url.openConnection(); - connection.setRequestMethod("GET"); - connection.connect(); - return connection; - } - - private void integrateAasWithRegistry(AssetAdministrationShell shell,String aasRegistryUrl, String aasRepositoryUrl) throws RepositoryRegistryLinkException{ - List aasRepositoryURLs = List.of(aasRepositoryUrl); - AttributeMapper attributeMapper = getAttributeMapper(); - - AssetAdministrationShellDescriptor descriptor = new AasDescriptorFactory(shell, aasRepositoryURLs, attributeMapper).create(); - - RegistryAndDiscoveryInterfaceApi registryApi = new RegistryAndDiscoveryInterfaceApi(aasRegistryUrl); - - try { - registryApi.postAssetAdministrationShellDescriptor(descriptor); - - } catch (ApiException e) { - throw new RepositoryRegistryLinkException(shell.getId(), e); - } - logger.info("Shell '{}' has been automatically linked with the Registry", shell.getId()); - - } - - private static AttributeMapper getAttributeMapper() { - Jackson2ObjectMapperBuilder builder = new Jackson2ObjectMapperBuilder().serializationInclusion(JsonInclude.Include.NON_NULL); - Aas4JHTTPSerializationExtension extension = new Aas4JHTTPSerializationExtension(); - extension.extend(builder); - ObjectMapper objectMapper = builder.build(); - AttributeMapper attributeMapper = new AttributeMapper(objectMapper); - return attributeMapper; - } } diff --git a/basyx.gateway/basyx.gateway-core/src/main/java/org/eclipse/digitaltwin/basyx/gateway/core/Gateway.java b/basyx.gateway/basyx.gateway-core/src/main/java/org/eclipse/digitaltwin/basyx/gateway/core/Gateway.java index dad0d9be7..522303862 100644 --- a/basyx.gateway/basyx.gateway-core/src/main/java/org/eclipse/digitaltwin/basyx/gateway/core/Gateway.java +++ b/basyx.gateway/basyx.gateway-core/src/main/java/org/eclipse/digitaltwin/basyx/gateway/core/Gateway.java @@ -1,8 +1,13 @@ -package org.eclipse.digitaltwin.basyx.gateway.core.feature; +package org.eclipse.digitaltwin.basyx.gateway.core; import org.eclipse.digitaltwin.aas4j.v3.model.AssetAdministrationShell; import org.eclipse.digitaltwin.basyx.gateway.core.exception.BaSyxComponentNotHealthyException; +/** + * Gateway Interface + * + * @author fried + */ public interface Gateway { public void createAAS(AssetAdministrationShell aas, String aasRepository, String aasRegistry) throws BaSyxComponentNotHealthyException; diff --git a/basyx.gateway/basyx.gateway-core/src/main/java/org/eclipse/digitaltwin/basyx/gateway/core/GatewayFactory.java b/basyx.gateway/basyx.gateway-core/src/main/java/org/eclipse/digitaltwin/basyx/gateway/core/GatewayFactory.java index b3683552e..54c8c156d 100644 --- a/basyx.gateway/basyx.gateway-core/src/main/java/org/eclipse/digitaltwin/basyx/gateway/core/GatewayFactory.java +++ b/basyx.gateway/basyx.gateway-core/src/main/java/org/eclipse/digitaltwin/basyx/gateway/core/GatewayFactory.java @@ -1,7 +1,12 @@ package org.eclipse.digitaltwin.basyx.gateway.core; -import org.eclipse.digitaltwin.basyx.gateway.core.feature.Gateway; +import org.eclipse.digitaltwin.basyx.gateway.core.Gateway; +/** + * Factory interface to create a Gateway instance + * + * @author fried + */ public interface GatewayFactory { public Gateway create(); diff --git a/basyx.gateway/basyx.gateway-core/src/main/java/org/eclipse/digitaltwin/basyx/gateway/core/feature/GatewayFeature.java b/basyx.gateway/basyx.gateway-core/src/main/java/org/eclipse/digitaltwin/basyx/gateway/core/feature/GatewayFeature.java index 2298e211c..0ed4d459f 100644 --- a/basyx.gateway/basyx.gateway-core/src/main/java/org/eclipse/digitaltwin/basyx/gateway/core/feature/GatewayFeature.java +++ b/basyx.gateway/basyx.gateway-core/src/main/java/org/eclipse/digitaltwin/basyx/gateway/core/feature/GatewayFeature.java @@ -3,6 +3,11 @@ import org.eclipse.digitaltwin.basyx.core.BaSyxFeature; import org.eclipse.digitaltwin.basyx.gateway.core.GatewayFactory; +/** + * Basic interface for Gateway Features + * + * @author fried + */ public interface GatewayFeature extends BaSyxFeature { } diff --git a/basyx.gateway/basyx.gateway-core/src/main/java/org/eclipse/digitaltwin/basyx/gateway/core/registryintegration/AASRegistryIntegrator.java b/basyx.gateway/basyx.gateway-core/src/main/java/org/eclipse/digitaltwin/basyx/gateway/core/registryintegration/AASRegistryIntegrator.java new file mode 100644 index 000000000..b34a6c411 --- /dev/null +++ b/basyx.gateway/basyx.gateway-core/src/main/java/org/eclipse/digitaltwin/basyx/gateway/core/registryintegration/AASRegistryIntegrator.java @@ -0,0 +1,88 @@ +/******************************************************************************* + * Copyright (C) 2024 the Eclipse BaSyx Authors + * + * Permission is hereby granted, free of charge, to any person obtaining + * a copy of this software and associated documentation files (the + * "Software"), to deal in the Software without restriction, including + * without limitation the rights to use, copy, modify, merge, publish, + * distribute, sublicense, and/or sell copies of the Software, and to + * permit persons to whom the Software is furnished to do so, subject to + * the following conditions: + * + * The above copyright notice and this permission notice shall be + * included in all copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, + * EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF + * MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND + * NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE + * LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION + * OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION + * WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. + * + * SPDX-License-Identifier: MIT + ******************************************************************************/ + +package org.eclipse.digitaltwin.basyx.gateway.core.registryintegration; + +import com.fasterxml.jackson.annotation.JsonInclude; +import com.fasterxml.jackson.databind.ObjectMapper; +import org.eclipse.digitaltwin.aas4j.v3.model.AssetAdministrationShell; +import org.eclipse.digitaltwin.basyx.aasregistry.client.ApiException; +import org.eclipse.digitaltwin.basyx.aasregistry.client.api.RegistryAndDiscoveryInterfaceApi; +import org.eclipse.digitaltwin.basyx.aasregistry.client.model.AssetAdministrationShellDescriptor; +import org.eclipse.digitaltwin.basyx.aasregistry.main.client.factory.AasDescriptorFactory; +import org.eclipse.digitaltwin.basyx.aasregistry.main.client.mapper.AttributeMapper; +import org.eclipse.digitaltwin.basyx.core.exceptions.RepositoryRegistryLinkException; +import org.eclipse.digitaltwin.basyx.http.Aas4JHTTPSerializationExtension; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; +import org.springframework.http.converter.json.Jackson2ObjectMapperBuilder; + +import java.util.List; + +/** + * Helper class to integrate AAS into a Registry + * + * @author fried + */ +public class AASRegistryIntegrator { + private final String aasRegistryUrl; + private Logger logger = LoggerFactory.getLogger(AASRegistryIntegrator.class); + + + public AASRegistryIntegrator(String aasRegistryURL){ + this.aasRegistryUrl = aasRegistryURL; + } + + public void registerAAS(AssetAdministrationShell aas, String aasURL){ + integrateAasWithRegistry(aas, aasRegistryUrl, aasURL); + } + + + private static AttributeMapper getAttributeMapper() { + Jackson2ObjectMapperBuilder builder = new Jackson2ObjectMapperBuilder().serializationInclusion(JsonInclude.Include.NON_NULL); + Aas4JHTTPSerializationExtension extension = new Aas4JHTTPSerializationExtension(); + extension.extend(builder); + ObjectMapper objectMapper = builder.build(); + AttributeMapper attributeMapper = new AttributeMapper(objectMapper); + return attributeMapper; + } + + private void integrateAasWithRegistry(AssetAdministrationShell shell,String aasRegistryUrl, String aasRepositoryUrl) throws RepositoryRegistryLinkException { + List aasRepositoryURLs = List.of(aasRepositoryUrl); + AttributeMapper attributeMapper = getAttributeMapper(); + + AssetAdministrationShellDescriptor descriptor = new AasDescriptorFactory(shell, aasRepositoryURLs, attributeMapper).create(); + + RegistryAndDiscoveryInterfaceApi registryApi = new RegistryAndDiscoveryInterfaceApi(aasRegistryUrl); + + try { + registryApi.postAssetAdministrationShellDescriptor(descriptor); + + } catch (ApiException e) { + throw new RepositoryRegistryLinkException(shell.getId(), e); + } + logger.info("Shell '{}' has been automatically linked with the Registry", shell.getId()); + } +} diff --git a/basyx.gateway/basyx.gateway-core/src/main/java/org/eclipse/digitaltwin/basyx/gateway/core/registryintegration/SubmodelRegistryIntegrator.java b/basyx.gateway/basyx.gateway-core/src/main/java/org/eclipse/digitaltwin/basyx/gateway/core/registryintegration/SubmodelRegistryIntegrator.java new file mode 100644 index 000000000..67f2e38ca --- /dev/null +++ b/basyx.gateway/basyx.gateway-core/src/main/java/org/eclipse/digitaltwin/basyx/gateway/core/registryintegration/SubmodelRegistryIntegrator.java @@ -0,0 +1,62 @@ +/******************************************************************************* + * Copyright (C) 2024 the Eclipse BaSyx Authors + * + * Permission is hereby granted, free of charge, to any person obtaining + * a copy of this software and associated documentation files (the + * "Software"), to deal in the Software without restriction, including + * without limitation the rights to use, copy, modify, merge, publish, + * distribute, sublicense, and/or sell copies of the Software, and to + * permit persons to whom the Software is furnished to do so, subject to + * the following conditions: + * + * The above copyright notice and this permission notice shall be + * included in all copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, + * EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF + * MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND + * NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE + * LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION + * OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION + * WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. + * + * SPDX-License-Identifier: MIT + ******************************************************************************/ + +package org.eclipse.digitaltwin.basyx.gateway.core.registryintegration; + +import com.fasterxml.jackson.annotation.JsonInclude; +import com.fasterxml.jackson.databind.ObjectMapper; +import org.apache.commons.lang3.NotImplementedException; +import org.eclipse.digitaltwin.aas4j.v3.model.AssetAdministrationShell; +import org.eclipse.digitaltwin.basyx.aasregistry.client.ApiException; +import org.eclipse.digitaltwin.basyx.aasregistry.client.api.RegistryAndDiscoveryInterfaceApi; +import org.eclipse.digitaltwin.basyx.aasregistry.client.model.AssetAdministrationShellDescriptor; +import org.eclipse.digitaltwin.basyx.aasregistry.main.client.factory.AasDescriptorFactory; +import org.eclipse.digitaltwin.basyx.aasregistry.main.client.mapper.AttributeMapper; +import org.eclipse.digitaltwin.basyx.core.exceptions.RepositoryRegistryLinkException; +import org.eclipse.digitaltwin.basyx.http.Aas4JHTTPSerializationExtension; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; +import org.springframework.http.converter.json.Jackson2ObjectMapperBuilder; + +import java.util.List; + +/** + * Helper class to integrate Submodel into a Registry + * + * @author fried + */ +public class SubmodelRegistryIntegrator { + private final String aasRegistryUrl; + private Logger logger = LoggerFactory.getLogger(SubmodelRegistryIntegrator.class); + + + public SubmodelRegistryIntegrator(String submodelRegistryURL){ + this.aasRegistryUrl = submodelRegistryURL; + } + + public void registerAAS(AssetAdministrationShell aas, String aasURL){ + throw new NotImplementedException(); + } +} diff --git a/basyx.gateway/basyx.gateway-core/src/main/java/org/eclipse/digitaltwin/basyx/gateway/core/utils/GatewayUtils.java b/basyx.gateway/basyx.gateway-core/src/main/java/org/eclipse/digitaltwin/basyx/gateway/core/utils/GatewayUtils.java new file mode 100644 index 000000000..5c0b8636d --- /dev/null +++ b/basyx.gateway/basyx.gateway-core/src/main/java/org/eclipse/digitaltwin/basyx/gateway/core/utils/GatewayUtils.java @@ -0,0 +1,134 @@ +/******************************************************************************* + * Copyright (C) 2024 the Eclipse BaSyx Authors + * + * Permission is hereby granted, free of charge, to any person obtaining + * a copy of this software and associated documentation files (the + * "Software"), to deal in the Software without restriction, including + * without limitation the rights to use, copy, modify, merge, publish, + * distribute, sublicense, and/or sell copies of the Software, and to + * permit persons to whom the Software is furnished to do so, subject to + * the following conditions: + * + * The above copyright notice and this permission notice shall be + * included in all copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, + * EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF + * MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND + * NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE + * LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION + * OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION + * WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. + * + * SPDX-License-Identifier: MIT + ******************************************************************************/ + +package org.eclipse.digitaltwin.basyx.gateway.core.utils; + +import org.eclipse.digitaltwin.aas4j.v3.model.AssetAdministrationShell; +import org.eclipse.digitaltwin.basyx.aasrepository.client.ConnectedAasRepository; +import org.eclipse.digitaltwin.basyx.core.exceptions.RepositoryRegistryLinkException; +import org.eclipse.digitaltwin.basyx.gateway.core.DefaultGateway; +import org.eclipse.digitaltwin.basyx.gateway.core.exception.BaSyxComponentNotHealthyException; +import org.eclipse.digitaltwin.basyx.gateway.core.exception.RegistryUnavailableException; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; + +import java.io.IOException; +import java.net.HttpURLConnection; +import java.net.URL; + +public class GatewayUtils { + private Logger logger = LoggerFactory.getLogger(GatewayUtils.class); + + + public static boolean isRegistryDefined(String aasRegistry) { + return aasRegistry != null; + } + + public void validateRepository(String aasRepository) throws BaSyxComponentNotHealthyException { + if (aasRepository == null) { + throw new UnsupportedOperationException("No AAS Repository configured"); + } + throwExceptionIfIsUnhealthyBaSyxRepository(aasRepository); + } + + public void validateRegistry(String aasRegistry) throws BaSyxComponentNotHealthyException { + throwExceptionIfIsUnhealthyBaSyxRegistry(aasRegistry); + } + + public void handleRegistryLinkException(ConnectedAasRepository aasRepo, AssetAdministrationShell aas, String aasRepository, String aasRegistry, RepositoryRegistryLinkException e) throws RegistryUnavailableException { + logger.error("Unable to link AAS {} with registry {}. Rolling back...", aas.getId(), aasRegistry); + aasRepo.deleteAas(aas.getId()); + logger.error("Rollback in AAS Repository {} completed.", aasRepository); + throw new RegistryUnavailableException("Unable to link AAS with registry. Changes in AAS Repository rolled back."); + } + + + public void throwExceptionIfIsUnhealthyBaSyxRepository(String componentURL) { + componentURL = formatURL(componentURL); + + if (isBaSyxComponent(componentURL,"/shells") && !isHealthy(componentURL)) { + throw new BaSyxComponentNotHealthyException(componentURL + " is not healthy"); + } + } + + public void throwExceptionIfIsUnhealthyBaSyxRegistry(String componentURL) { + componentURL = formatURL(componentURL); + + if (isBaSyxComponent(componentURL,"/shell-descriptors") && !isHealthy(componentURL)) { + throw new BaSyxComponentNotHealthyException(componentURL + " is not healthy"); + } + } + + public String formatURL(String componentURL) { + try { + URL url = new URL(componentURL); + String protocol = url.getProtocol(); + String host = url.getHost(); + int port = url.getPort(); + if (port == -1) { // no port specified, use default port + port = url.getDefaultPort(); + } + componentURL = protocol + "://" + host + ":" + port; + } catch (Exception e) { + logger.error(e.getMessage()); + } + return componentURL; + } + + public boolean isBaSyxComponent(String componentURL, String endpointToCheck) { + try { + HttpURLConnection connection = getRequest(componentURL, "/shells"); + + String aasMiddleware = connection.getHeaderField("aas_middleware"); + + return "BaSyx".equals(aasMiddleware); + } catch (Exception e) { + logger.error(e.getMessage()); + return false; + } + } + + public boolean isHealthy(String componentURL){ + try { + HttpURLConnection connection = getRequest(componentURL, "/actuator/health"); + + String body = new String(connection.getInputStream().readAllBytes()); + + return connection.getResponseCode() == 200 && body.contains("UP"); + + } catch (Exception e) { + logger.error(e.getMessage()); + return false; + } + } + + public static HttpURLConnection getRequest(String componentURL, String path) throws IOException { + URL url = new URL(componentURL + path); + HttpURLConnection connection = (HttpURLConnection) url.openConnection(); + connection.setRequestMethod("GET"); + connection.connect(); + return connection; + } +} diff --git a/basyx.gateway/basyx.gateway-core/src/test/java/org/eclipse/digitaltwin/basyx/gateway/TestGateway.java b/basyx.gateway/basyx.gateway-core/src/test/java/org/eclipse/digitaltwin/basyx/gateway/TestGateway.java index 81f5acf5b..967296330 100644 --- a/basyx.gateway/basyx.gateway-core/src/test/java/org/eclipse/digitaltwin/basyx/gateway/TestGateway.java +++ b/basyx.gateway/basyx.gateway-core/src/test/java/org/eclipse/digitaltwin/basyx/gateway/TestGateway.java @@ -32,7 +32,7 @@ import org.eclipse.digitaltwin.basyx.gateway.core.DefaultGateway; import org.eclipse.digitaltwin.basyx.gateway.core.exception.BaSyxComponentNotHealthyException; import org.eclipse.digitaltwin.basyx.gateway.core.exception.RegistryUnavailableException; -import org.eclipse.digitaltwin.basyx.gateway.core.feature.Gateway; +import org.eclipse.digitaltwin.basyx.gateway.core.Gateway; import org.eclipse.digitaltwin.basyx.http.Base64UrlEncodedIdentifier; import org.junit.After; import org.junit.Before; diff --git a/basyx.gateway/basyx.gateway-http/src/main/java/org/eclipse/digitaltwin/basyx/gateway/http/GatewayHTTPApiController.java b/basyx.gateway/basyx.gateway-http/src/main/java/org/eclipse/digitaltwin/basyx/gateway/http/GatewayHTTPApiController.java index c54bca750..327d6033f 100644 --- a/basyx.gateway/basyx.gateway-http/src/main/java/org/eclipse/digitaltwin/basyx/gateway/http/GatewayHTTPApiController.java +++ b/basyx.gateway/basyx.gateway-http/src/main/java/org/eclipse/digitaltwin/basyx/gateway/http/GatewayHTTPApiController.java @@ -27,7 +27,7 @@ import org.eclipse.digitaltwin.aas4j.v3.model.AssetAdministrationShell; import org.eclipse.digitaltwin.basyx.gateway.core.exception.BaSyxComponentNotHealthyException; -import org.eclipse.digitaltwin.basyx.gateway.core.feature.Gateway; +import org.eclipse.digitaltwin.basyx.gateway.core.Gateway; import org.springframework.beans.factory.annotation.Value; import org.springframework.boot.autoconfigure.condition.ConditionalOnProperty; import org.springframework.http.HttpStatus; diff --git a/basyx.gateway/basyx.gateway.component/src/main/java/org/eclipse/digitaltwin/basyx/gateway/component/GatewayComponentConfiguration.java b/basyx.gateway/basyx.gateway.component/src/main/java/org/eclipse/digitaltwin/basyx/gateway/component/GatewayComponentConfiguration.java index 95e9ed656..d2070b46e 100644 --- a/basyx.gateway/basyx.gateway.component/src/main/java/org/eclipse/digitaltwin/basyx/gateway/component/GatewayComponentConfiguration.java +++ b/basyx.gateway/basyx.gateway.component/src/main/java/org/eclipse/digitaltwin/basyx/gateway/component/GatewayComponentConfiguration.java @@ -26,7 +26,7 @@ package org.eclipse.digitaltwin.basyx.gateway.component; import org.eclipse.digitaltwin.basyx.gateway.core.DefaultGateway; -import org.eclipse.digitaltwin.basyx.gateway.core.feature.Gateway; +import org.eclipse.digitaltwin.basyx.gateway.core.Gateway; import org.springframework.context.annotation.Bean; import org.springframework.context.annotation.Configuration;