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