diff --git a/.github/workflows/basyx_test.yml b/.github/workflows/basyx_test.yml index 1187244a2..9f49ffb5a 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: Gateway 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..f00e1cca3 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: Gateway - 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 diff --git a/basyx.gateway/basyx.gateway-core/pom.xml b/basyx.gateway/basyx.gateway-core/pom.xml new file mode 100644 index 000000000..2ac5d2c33 --- /dev/null +++ b/basyx.gateway/basyx.gateway-core/pom.xml @@ -0,0 +1,46 @@ + + + 4.0.0 + + org.eclipse.digitaltwin.basyx + basyx.gateway + ${revision} + + + basyx.gateway-core + BaSyx Gateway HTTP + BaSyx Gateway HTTP + + + 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 + + + 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..05fd5c843 --- /dev/null +++ b/basyx.gateway/basyx.gateway-core/src/main/java/org/eclipse/digitaltwin/basyx/gateway/core/DefaultGateway.java @@ -0,0 +1,64 @@ +/******************************************************************************* + * 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.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.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 final GatewayUtils utils = new GatewayUtils(); + + @Override + public void createAAS(AssetAdministrationShell aas, String aasRepository, String aasRegistry) throws BaSyxComponentNotHealthyException { + utils.validateRepository(aasRepository); + + ConnectedAasRepository aasRepo = new ConnectedAasRepository(aasRepository); + aasRepo.createAas(aas); + + if (!GatewayUtils.isRegistryDefined(aasRegistry)) { + return; + } + + utils.validateRegistry(aasRegistry); + + try { + AASRegistryIntegrator integrator = new AASRegistryIntegrator(aasRegistry); + integrator.registerAAS(aas, aasRepository); + } catch (RepositoryRegistryLinkException e) { + utils.handleRegistryLinkException(aasRepo, aas, aasRepository, aasRegistry, e); + } + } +} 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..522303862 --- /dev/null +++ b/basyx.gateway/basyx.gateway-core/src/main/java/org/eclipse/digitaltwin/basyx/gateway/core/Gateway.java @@ -0,0 +1,14 @@ +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 new file mode 100644 index 000000000..54c8c156d --- /dev/null +++ b/basyx.gateway/basyx.gateway-core/src/main/java/org/eclipse/digitaltwin/basyx/gateway/core/GatewayFactory.java @@ -0,0 +1,13 @@ +package org.eclipse.digitaltwin.basyx.gateway.core; + +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/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/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/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..0ed4d459f --- /dev/null +++ b/basyx.gateway/basyx.gateway-core/src/main/java/org/eclipse/digitaltwin/basyx/gateway/core/feature/GatewayFeature.java @@ -0,0 +1,13 @@ +package org.eclipse.digitaltwin.basyx.gateway.core.feature; + +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 new file mode 100644 index 000000000..967296330 --- /dev/null +++ b/basyx.gateway/basyx.gateway-core/src/test/java/org/eclipse/digitaltwin/basyx/gateway/TestGateway.java @@ -0,0 +1,173 @@ +/******************************************************************************* + * 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.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.exception.RegistryUnavailableException; +import org.eclipse.digitaltwin.basyx.gateway.core.Gateway; +import org.eclipse.digitaltwin.basyx.http.Base64UrlEncodedIdentifier; +import org.junit.After; +import org.junit.Before; +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 { + + 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; + + @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("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("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(), 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) { + new MockServerClient("localhost", 6006) + .when(org.mockserver.model.HttpRequest.request() + .withMethod("GET") + .withPath("/shells")) + .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("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() + .withMethod("GET") + .withPath("/actuator/health")) + .respond(HttpResponse.response() + .withStatusCode(HttpStatus.SC_OK) + .withBody(responseBody)); + } + + 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 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(); + } +} diff --git a/basyx.gateway/basyx.gateway-http/pom.xml b/basyx.gateway/basyx.gateway-http/pom.xml new file mode 100644 index 000000000..0fb1df80b --- /dev/null +++ b/basyx.gateway/basyx.gateway-http/pom.xml @@ -0,0 +1,60 @@ + + + 4.0.0 + + org.eclipse.digitaltwin.basyx + basyx.gateway + ${revision} + + + basyx.gateway-http + BaSyx Gateway HTTP + BaSyx Gateway HTTP + + + + org.eclipse.digitaltwin.basyx + basyx.gateway-core + + + 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..32eaa767c --- /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 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 new file mode 100644 index 000000000..327d6033f --- /dev/null +++ b/basyx.gateway/basyx.gateway-http/src/main/java/org/eclipse/digitaltwin/basyx/gateway/http/GatewayHTTPApiController.java @@ -0,0 +1,74 @@ +/******************************************************************************* + * 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.eclipse.digitaltwin.basyx.gateway.core.exception.BaSyxComponentNotHealthyException; +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; +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; + + 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 new ResponseEntity(body, HttpStatus.CREATED); + } + + private static boolean isAnyURLSet(String url1, String url2) { + return url1 != null || url2 != null; + } + + +} 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"] diff --git a/basyx.gateway/basyx.gateway.component/pom.xml b/basyx.gateway/basyx.gateway.component/pom.xml new file mode 100644 index 000000000..7eecfef9b --- /dev/null +++ b/basyx.gateway/basyx.gateway.component/pom.xml @@ -0,0 +1,149 @@ + + + 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.aasrepository-client + + + org.eclipse.digitaltwin.basyx + basyx.gateway-core + + + 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 + + + + + + + org.springframework.boot + spring-boot-maven-plugin + + exec + + + + + + + + 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 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..c458e9564 --- /dev/null +++ b/basyx.gateway/basyx.gateway.component/src/main/java/org/eclipse/digitaltwin/basyx/gateway/component/ConfigurationGuard.java @@ -0,0 +1,106 @@ +/******************************************************************************* + * 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.slf4j.Logger; +import org.slf4j.LoggerFactory; +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; + +/** + * 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); + + @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); + } + + logger.info(":::::::::::::::: BaSyx Gateway Configuration ::::::::::::::::"); + if (aasRepositoryURL != null) { + logger.info(":: Default AAS Repository URL: " + aasRepositoryURL); + } + if (submodelRepositoryURL != null) { + logger.info(":: Default Submodel Repository URL: " + submodelRepositoryURL); + } + if (aasRegistryURL != null) { + logger.info(":: Default AAS Registry URL: " + aasRegistryURL); + } + if (submodelRegistryURL != null) { + logger.info(":: Default Submodel Registry URL: " + submodelRegistryURL); + } + logger.info(":::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::\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..e6cbd594a --- /dev/null +++ b/basyx.gateway/basyx.gateway.component/src/main/java/org/eclipse/digitaltwin/basyx/gateway/component/GatewayComponent.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.springframework.boot.SpringApplication; +import org.springframework.boot.autoconfigure.SpringBootApplication; +import org.springframework.boot.autoconfigure.data.mongo.MongoDataAutoConfiguration; +import org.springframework.boot.autoconfigure.mongo.MongoAutoConfiguration; + +@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/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..d2070b46e --- /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.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/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/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..b3934437f --- /dev/null +++ b/basyx.gateway/basyx.gateway.component/src/main/resources/application.properties @@ -0,0 +1,14 @@ +server.port=5999 + + +####################################################### +# Default 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/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" diff --git a/pom.xml b/pom.xml index 953e70db9..eb45444cb 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 @@ -773,6 +774,22 @@ basyx.submodelregistry-client-native ${revision} + + + org.eclipse.digitaltwin.basyx + basyx.gateway-http + ${revision} + + + org.eclipse.digitaltwin.basyx + basyx.gateway.component + ${revision} + + + org.eclipse.digitaltwin.basyx + basyx.gateway-core + ${revision} + @@ -1211,6 +1228,25 @@ ${revision} tests + + + org.eclipse.digitaltwin.basyx + basyx.gateway-http + ${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