diff --git a/basyx.aasregistry/basyx.aasregistry-feature-hierarchy-example/Readme.md b/basyx.aasregistry/basyx.aasregistry-feature-hierarchy-example/Readme.md new file mode 100644 index 000000000..e2fb0cfb6 --- /dev/null +++ b/basyx.aasregistry/basyx.aasregistry-feature-hierarchy-example/Readme.md @@ -0,0 +1,42 @@ +# AssetAdministrationShell Registry - Hierarchy - Example + +This example showcases the working principle of the hierarchical registries feature. + +## Scenario description + +```mermaid +sequenceDiagram + actor Client + participant Root as Root Registry + participant Delegated as Delegated Registry + + Client ->> Root: Request resolution for AAS-ID "http://delegated-aas-registry:8080/test/aas" + activate Root + + Root ->> Root: Check local records + alt Not found locally + Root ->> Root: Determine registry based on URI prefix + Root ->> Delegated: Resolve AAS-ID "http://delegated-aas-registry:8080/test/aas" at registry.delegated-aas-registry:8080 + activate Delegated + Delegated ->> Delegated: Resolve AAS-ID + Delegated ->> Root: Resolution result + deactivate Delegated + end + + Root ->> Client: Return resolution result + deactivate Root +``` + +## Running the scenario + +In order to run the example, please make sure that all aasregistries maven modules are correctly installed in your local Maven repository. + +1. Generate the Docker image: `mvn clean install -Ddocker.namespace=aas-registry-test` + +2. Run the docker compose: `docker compose up` + +Two containers should start: (1) one for the root AAS Registry - to which the http request are going to be made; (2) one for the delegated AAS Registry - to which requests may be delegated to. + +They are visibile within the bridged Docker network as (1) aas-registry-root:8080 and (2) registry.delegated-aas-registry:8080 + +3. Run the scenario [HierarchicalAasRegistryIT](/src/test/java/org/eclipse/digitaltwin/basyx/aasregistry/feature/hierarchy/example/HierachicalAasRegistryIT.java) diff --git a/basyx.aasregistry/basyx.aasregistry-feature-hierarchy-example/aas-registry-delegated.yml b/basyx.aasregistry/basyx.aasregistry-feature-hierarchy-example/aas-registry-delegated.yml new file mode 100644 index 000000000..eb3b0feb3 --- /dev/null +++ b/basyx.aasregistry/basyx.aasregistry-feature-hierarchy-example/aas-registry-delegated.yml @@ -0,0 +1,5 @@ +--- +basyx: + cors: + allowed-origins: "*" + allowed-methods: "GET,POST,PATCH,DELETE,PUT,OPTIONS,HEAD" \ No newline at end of file diff --git a/basyx.aasregistry/basyx.aasregistry-feature-hierarchy-example/docker-compose.yml b/basyx.aasregistry/basyx.aasregistry-feature-hierarchy-example/docker-compose.yml new file mode 100644 index 000000000..adc3e0f24 --- /dev/null +++ b/basyx.aasregistry/basyx.aasregistry-feature-hierarchy-example/docker-compose.yml @@ -0,0 +1,29 @@ +version: "3.9" +services: + + aas-registry-root: + image: aas-registry-test/aas-registry-feature-hierarchy-example:2.0.0-SNAPSHOT + container_name: aas-registry-root + ports: + - "8051:8080" + environment: + SERVER_SERVLET_CONTEXT_PATH: / + networks: + - basyx-aasregistry-feature-hierarchy-example + + registry.delegated-aas-registry: + image: eclipsebasyx/aas-registry-log-mem:2.0.0-SNAPSHOT + container_name: registry.delegated-aas-registry + ports: + - "8052:8080" + environment: + SERVER_SERVLET_CONTEXT_PATH: / + volumes: + - ./aas-registry-delegated.yml:/workspace/config/application.yml + networks: + - basyx-aasregistry-feature-hierarchy-example + +networks: + basyx-aasregistry-feature-hierarchy-example: + name: basyx-aasregistry-feature-hierarchy-example + driver: bridge diff --git a/basyx.aasregistry/basyx.aasregistry-feature-hierarchy-example/pom.xml b/basyx.aasregistry/basyx.aasregistry-feature-hierarchy-example/pom.xml new file mode 100644 index 000000000..e1ad0ecce --- /dev/null +++ b/basyx.aasregistry/basyx.aasregistry-feature-hierarchy-example/pom.xml @@ -0,0 +1,35 @@ + + 4.0.0 + + + org.eclipse.digitaltwin.basyx + basyx.aasregistry + ${revision} + + + basyx.aasregistry-feature-hierarchy-example + BaSyx AAS Registry Feature Hierarchy Example + BaSyx AAS Registry Feature Hierarchy Example + + + 2020.0.4 + org.eclipse.digitaltwin.basyx.aasregistry.service.OpenApiGeneratorApplication + aas-registry-feature-hierarchy-example + + + + + org.eclipse.digitaltwin.basyx + basyx.aasregistry-service-release-log-mem + ${revision} + + + org.eclipse.digitaltwin.basyx + basyx.aasregistry-feature-hierarchy + tests + test + + + \ No newline at end of file diff --git a/basyx.aasregistry/basyx.aasregistry-feature-hierarchy-example/src/main/docker/Dockerfile b/basyx.aasregistry/basyx.aasregistry-feature-hierarchy-example/src/main/docker/Dockerfile new file mode 100644 index 000000000..d0f923d2b --- /dev/null +++ b/basyx.aasregistry/basyx.aasregistry-feature-hierarchy-example/src/main/docker/Dockerfile @@ -0,0 +1,21 @@ +FROM eclipse-temurin:17 as builder +COPY maven/${project.build.finalName}.jar ./ +RUN java -Djarmode=layertools -jar ${project.build.finalName}.jar extract + +FROM eclipse-temurin:17 +RUN mkdir /workspace +WORKDIR /workspace +COPY --from=builder dependencies/ ./ +COPY --from=builder snapshot-dependencies/ ./ +RUN true +COPY --from=builder spring-boot-loader/ ./ +COPY --from=builder application/ ./ +ENV SPRING_PROFILES_ACTIVE=logEvents,inMemoryStorage,hierarchy +ARG PORT=8080 +ENV SERVER_PORT=${PORT} +ARG CONTEXT_PATH=/ +ENV SERVER_SERVLET_CONTEXT_PATH=${CONTEXT_PATH} +EXPOSE ${SERVER_PORT} +HEALTHCHECK --interval=30s --timeout=3s --retries=3 --start-period=15s CMD curl --fail http://localhost:${SERVER_PORT}${SERVER_SERVLET_CONTEXT_PATH%/}/actuator/health || exit 1 +ENTRYPOINT ["java", "-Djava.security.egd=file:/dev/.urandom", "org.springframework.boot.loader.launch.JarLauncher"] + diff --git a/basyx.aasregistry/basyx.aasregistry-feature-hierarchy-example/src/main/resources/application-hierarchy.yml b/basyx.aasregistry/basyx.aasregistry-feature-hierarchy-example/src/main/resources/application-hierarchy.yml new file mode 100644 index 000000000..2c39855f5 --- /dev/null +++ b/basyx.aasregistry/basyx.aasregistry-feature-hierarchy-example/src/main/resources/application-hierarchy.yml @@ -0,0 +1,9 @@ +--- +basyx: + cors: + allowed-origins: "*" + allowed-methods: "GET,POST,PATCH,DELETE,PUT,OPTIONS,HEAD" + feature: + hierarchy: + enabled: true + \ No newline at end of file diff --git a/basyx.aasregistry/basyx.aasregistry-feature-hierarchy-example/src/test/java/org/eclipse/digitaltwin/basyx/aasregistry/feature/hierarchy/example/HierachicalAasRegistryIT.java b/basyx.aasregistry/basyx.aasregistry-feature-hierarchy-example/src/test/java/org/eclipse/digitaltwin/basyx/aasregistry/feature/hierarchy/example/HierachicalAasRegistryIT.java new file mode 100644 index 000000000..1266d3d0a --- /dev/null +++ b/basyx.aasregistry/basyx.aasregistry-feature-hierarchy-example/src/test/java/org/eclipse/digitaltwin/basyx/aasregistry/feature/hierarchy/example/HierachicalAasRegistryIT.java @@ -0,0 +1,66 @@ +/******************************************************************************* + * 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.aasregistry.feature.hierarchy.example; + +import org.eclipse.digitaltwin.basyx.aasregistry.client.api.RegistryAndDiscoveryInterfaceApi; +import org.eclipse.digitaltwin.basyx.aasregistry.feature.hierarchy.DummyDescriptorFactory; +import org.eclipse.digitaltwin.basyx.aasregistry.feature.hierarchy.HierarchicalAasRegistryTestSuite; + +/** + * HierachicalAasRegistryIT + * + * @author mateusmolina + * + */ +public class HierachicalAasRegistryIT extends HierarchicalAasRegistryTestSuite { + + private static final String ROOT_REGISTRY_URL = "http://localhost:8051"; + private static final String DELEGATED_REGISTRY_URL = "http://localhost:8052"; + + private static final String REPO_BASE_URL = "http://localhost:8080"; + private static final String DELEGATED_AASID = "http://delegated-aas-registry:8080/test/aas"; + + private static final DummyDescriptorFactory factory = new DummyDescriptorFactory(REPO_BASE_URL, DELEGATED_AASID); + + private static final RegistryAndDiscoveryInterfaceApi rootRegistryApi = new RegistryAndDiscoveryInterfaceApi(ROOT_REGISTRY_URL); + private static final RegistryAndDiscoveryInterfaceApi delegatedRegistryApi = new RegistryAndDiscoveryInterfaceApi(DELEGATED_REGISTRY_URL); + + @Override + protected RegistryAndDiscoveryInterfaceApi getRootRegistryApi() { + return rootRegistryApi; + } + + @Override + protected RegistryAndDiscoveryInterfaceApi getDelegatedRegistryApi() { + return delegatedRegistryApi; + } + + @Override + protected DummyDescriptorFactory getDescriptorFactory() { + return factory; + } + +} diff --git a/basyx.aasregistry/basyx.aasregistry-feature-hierarchy/Readme.md b/basyx.aasregistry/basyx.aasregistry-feature-hierarchy/Readme.md new file mode 100644 index 000000000..62fc04185 --- /dev/null +++ b/basyx.aasregistry/basyx.aasregistry-feature-hierarchy/Readme.md @@ -0,0 +1,31 @@ +# AssetAdministrationShell Registry - Hierarchy + +The Hierarchical AasRegistry Feature enhances the availability of data across multiple registries by allowing retrieval requests to be delegated to another AasRegistry. + +This feature allows the creation of a hierarchical structure of registries, where a registry can delegate retrieval requests to another registry when a given descriptor is not found in its storage. + +## Configuration + +### Enabling the Feature + +To enable the Hierarchical AasRegistry Feature, add the following property to your Spring application's configuration: + +```properties +basyx.feature.hierarchy.enabled=true +``` + +### Delegation Strategy + +Currently, only one delegation strategy is implemented: + +#### Prefix Delegation Strategy + +Delegates requests based on the `aasDescriptorId` value. If the ID is an URL, a prefix (defaut `registry`) is appended to the URL and used as delegation endpoint. + +The prefix can be configured by the property `basyx.feature.hierarchy.prefix`. Please refer to the example below: + +```properties +basyx.feature.hierarchy.prefix=registry +``` + +If this property is left with an empty string, no prefix is appended to the URL contained in the `aasDecriptorId`. diff --git a/basyx.aasregistry/basyx.aasregistry-feature-hierarchy/pom.xml b/basyx.aasregistry/basyx.aasregistry-feature-hierarchy/pom.xml new file mode 100644 index 000000000..76fd301fe --- /dev/null +++ b/basyx.aasregistry/basyx.aasregistry-feature-hierarchy/pom.xml @@ -0,0 +1,38 @@ + + 4.0.0 + + + org.eclipse.digitaltwin.basyx + basyx.aasregistry + ${revision} + + + basyx.aasregistry-feature-hierarchy + BaSyx AAS Registry feature-hierarchy + + + + org.eclipse.digitaltwin.basyx + basyx.hierarchy + + + org.eclipse.digitaltwin.basyx + basyx.aasregistry-service + + + org.eclipse.digitaltwin.basyx + basyx.aasregistry-service-basemodel + + + org.eclipse.digitaltwin.basyx + basyx.aasregistry-client-native + + + org.eclipse.digitaltwin.basyx + basyx.aasregistry-service-inmemory-storage + test + + + \ No newline at end of file diff --git a/basyx.aasregistry/basyx.aasregistry-feature-hierarchy/src/main/java/org/eclipse/digitaltwin/basyx/aasregistry/feature/hierarchy/AasRegistryModelMapper.java b/basyx.aasregistry/basyx.aasregistry-feature-hierarchy/src/main/java/org/eclipse/digitaltwin/basyx/aasregistry/feature/hierarchy/AasRegistryModelMapper.java new file mode 100644 index 000000000..791071dfc --- /dev/null +++ b/basyx.aasregistry/basyx.aasregistry-feature-hierarchy/src/main/java/org/eclipse/digitaltwin/basyx/aasregistry/feature/hierarchy/AasRegistryModelMapper.java @@ -0,0 +1,97 @@ +/******************************************************************************* + * 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.aasregistry.feature.hierarchy; + +import java.util.List; + +import org.eclipse.digitaltwin.basyx.aasregistry.client.ApiException; +import org.eclipse.digitaltwin.basyx.aasregistry.client.model.GetSubmodelDescriptorsResult; +import org.eclipse.digitaltwin.basyx.aasregistry.model.AssetAdministrationShellDescriptor; +import org.eclipse.digitaltwin.basyx.aasregistry.model.SubmodelDescriptor; +import org.eclipse.digitaltwin.basyx.aasregistry.service.errors.AasDescriptorNotFoundException; +import org.eclipse.digitaltwin.basyx.aasregistry.service.errors.SubmodelNotFoundException; +import org.eclipse.digitaltwin.basyx.core.pagination.CursorResult; +import org.springframework.http.HttpStatus; +import org.springframework.http.HttpStatusCode; + +import com.fasterxml.jackson.core.type.TypeReference; +import com.fasterxml.jackson.databind.ObjectMapper; + +/** + * Handles internal model mapping for {@link HierarchicalAasRegistryStorage} + * + * @author mateusmolina + * + */ +final class AasRegistryModelMapper { + + private static final ObjectMapper objectMapper = new ObjectMapper(); + + private AasRegistryModelMapper() { + } + + static org.eclipse.digitaltwin.basyx.aasregistry.client.model.AssetAdministrationShellDescriptor mapEqModel(AssetAdministrationShellDescriptor aasRegistryDescriptor) { + return objectMapper.convertValue(aasRegistryDescriptor, org.eclipse.digitaltwin.basyx.aasregistry.client.model.AssetAdministrationShellDescriptor.class); + } + + static AssetAdministrationShellDescriptor mapEqModel(org.eclipse.digitaltwin.basyx.aasregistry.client.model.AssetAdministrationShellDescriptor aasRegistryDescriptor) { + return objectMapper.convertValue(aasRegistryDescriptor, AssetAdministrationShellDescriptor.class); + } + + static org.eclipse.digitaltwin.basyx.aasregistry.client.model.SubmodelDescriptor mapEqModel(SubmodelDescriptor smRegistryDescriptor) { + return objectMapper.convertValue(smRegistryDescriptor, org.eclipse.digitaltwin.basyx.aasregistry.client.model.SubmodelDescriptor.class); + } + + static SubmodelDescriptor mapEqModel(org.eclipse.digitaltwin.basyx.aasregistry.client.model.SubmodelDescriptor smRegistryDescriptor) { + return objectMapper.convertValue(smRegistryDescriptor, SubmodelDescriptor.class); + } + + static CursorResult> mapEqModel(GetSubmodelDescriptorsResult descriptorResult) { + List submodelDescs = objectMapper.convertValue(descriptorResult.getResult(), new TypeReference>() { + }); + return new CursorResult<>(descriptorResult.getPagingMetadata().getCursor(), submodelDescs); + } + + static RuntimeException mapApiException(ApiException e, String aasDescriptorId) { + if (HttpStatusCode.valueOf(e.getCode()).equals(HttpStatus.NOT_FOUND)) + return new AasDescriptorNotFoundException(aasDescriptorId); + + return new RuntimeException(e); + } + + static RuntimeException mapApiException(ApiException e, String aasDescriptorId, String smId) { + if (HttpStatusCode.valueOf(e.getCode()).equals(HttpStatus.NOT_FOUND) && checkIfSubmodelNotFound(e, aasDescriptorId, smId)) + return new SubmodelNotFoundException(aasDescriptorId, smId); + + return mapApiException(e, aasDescriptorId); + } + + private static boolean checkIfSubmodelNotFound(ApiException e, String aasDescriptorId, String smId) { + SubmodelNotFoundException expectedException = new SubmodelNotFoundException(aasDescriptorId, smId); + return e.getMessage().contains(expectedException.getReason()); + } + +} diff --git a/basyx.aasregistry/basyx.aasregistry-feature-hierarchy/src/main/java/org/eclipse/digitaltwin/basyx/aasregistry/feature/hierarchy/HierarchicalAasRegistryFeature.java b/basyx.aasregistry/basyx.aasregistry-feature-hierarchy/src/main/java/org/eclipse/digitaltwin/basyx/aasregistry/feature/hierarchy/HierarchicalAasRegistryFeature.java new file mode 100644 index 000000000..e0ed87497 --- /dev/null +++ b/basyx.aasregistry/basyx.aasregistry-feature-hierarchy/src/main/java/org/eclipse/digitaltwin/basyx/aasregistry/feature/hierarchy/HierarchicalAasRegistryFeature.java @@ -0,0 +1,76 @@ +/******************************************************************************* + * 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.aasregistry.feature.hierarchy; + +import org.eclipse.digitaltwin.basyx.aasregistry.service.storage.AasRegistryStorage; +import org.eclipse.digitaltwin.basyx.aasregistry.service.storage.AasRegistryStorageFeature; +import org.eclipse.digitaltwin.basyx.common.hierarchy.CommonHierarchyProperties; +import org.eclipse.digitaltwin.basyx.common.hierarchy.delegation.DelegationStrategy; +import org.springframework.beans.factory.annotation.Value; +import org.springframework.boot.autoconfigure.condition.ConditionalOnExpression; +import org.springframework.core.annotation.Order; +import org.springframework.stereotype.Component; + +/** + * Hierarchical {@link AasRegistryStorage} feature + * + * When this feature is enabled, retrieval requests will be delegated to the + * next AasRegistry. + * + * The next AasRegistry is selected via a {@link DelegationStrategy} + * + * @author mateusmolina + */ +@Component +@ConditionalOnExpression("${" + CommonHierarchyProperties.HIERARCHY_FEATURE_ENABLED + ":false}") +@Order(1) +public class HierarchicalAasRegistryFeature implements AasRegistryStorageFeature { + + @Value("${" + CommonHierarchyProperties.HIERARCHY_FEATURE_ENABLED + "}") + private boolean enabled; + + private DelegationStrategy delegationStrategy; + + public HierarchicalAasRegistryFeature(DelegationStrategy delegationStrategy) { + this.delegationStrategy = delegationStrategy; + } + + @Override + public AasRegistryStorage decorate(AasRegistryStorage storage) { + return new HierarchicalAasRegistryStorage(storage, delegationStrategy); + } + + @Override + public String getName() { + return "AasRegistry Hierarchy"; + } + + @Override + public boolean isEnabled() { + return enabled; + } + +} \ No newline at end of file diff --git a/basyx.aasregistry/basyx.aasregistry-feature-hierarchy/src/main/java/org/eclipse/digitaltwin/basyx/aasregistry/feature/hierarchy/HierarchicalAasRegistryStorage.java b/basyx.aasregistry/basyx.aasregistry-feature-hierarchy/src/main/java/org/eclipse/digitaltwin/basyx/aasregistry/feature/hierarchy/HierarchicalAasRegistryStorage.java new file mode 100644 index 000000000..428e3e5c6 --- /dev/null +++ b/basyx.aasregistry/basyx.aasregistry-feature-hierarchy/src/main/java/org/eclipse/digitaltwin/basyx/aasregistry/feature/hierarchy/HierarchicalAasRegistryStorage.java @@ -0,0 +1,151 @@ +/******************************************************************************* + * 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.aasregistry.feature.hierarchy; + +import java.util.List; +import java.util.Set; + +import org.eclipse.digitaltwin.basyx.aasregistry.client.ApiException; +import org.eclipse.digitaltwin.basyx.aasregistry.client.api.RegistryAndDiscoveryInterfaceApi; +import org.eclipse.digitaltwin.basyx.aasregistry.model.AssetAdministrationShellDescriptor; +import org.eclipse.digitaltwin.basyx.aasregistry.model.ShellDescriptorSearchRequest; +import org.eclipse.digitaltwin.basyx.aasregistry.model.ShellDescriptorSearchResponse; +import org.eclipse.digitaltwin.basyx.aasregistry.model.SubmodelDescriptor; +import org.eclipse.digitaltwin.basyx.aasregistry.service.errors.AasDescriptorAlreadyExistsException; +import org.eclipse.digitaltwin.basyx.aasregistry.service.errors.AasDescriptorNotFoundException; +import org.eclipse.digitaltwin.basyx.aasregistry.service.errors.SubmodelAlreadyExistsException; +import org.eclipse.digitaltwin.basyx.aasregistry.service.errors.SubmodelNotFoundException; +import org.eclipse.digitaltwin.basyx.aasregistry.service.storage.AasRegistryStorage; +import org.eclipse.digitaltwin.basyx.aasregistry.service.storage.DescriptorFilter; +import org.eclipse.digitaltwin.basyx.common.hierarchy.delegation.DelegationStrategy; +import org.eclipse.digitaltwin.basyx.core.pagination.CursorResult; +import org.eclipse.digitaltwin.basyx.core.pagination.PaginationInfo; + +/** + * Decorator for Hierarchical {@link AasRegistryStorage} + * + * @author mateusmolina + */ +public class HierarchicalAasRegistryStorage implements AasRegistryStorage { + + private final AasRegistryStorage decorated; + + private final DelegationStrategy delegationStrategy; + + public HierarchicalAasRegistryStorage(AasRegistryStorage decorated, DelegationStrategy delegationStrategy) { + this.decorated = decorated; + this.delegationStrategy = delegationStrategy; + } + + @Override + public CursorResult> getAllAasDescriptors(PaginationInfo pRequest, DescriptorFilter filter) { + return decorated.getAllAasDescriptors(pRequest, filter); + } + + @Override + public AssetAdministrationShellDescriptor getAasDescriptor(String aasDescriptorId) throws AasDescriptorNotFoundException { + try { + return decorated.getAasDescriptor(aasDescriptorId); + } catch (AasDescriptorNotFoundException e) { + try { + return AasRegistryModelMapper.mapEqModel(getDelegatedRegistryApi(aasDescriptorId).getAssetAdministrationShellDescriptorById(aasDescriptorId)); + } catch (ApiException e1) { + throw AasRegistryModelMapper.mapApiException(e1, aasDescriptorId); + } + } + } + + @Override + public void insertAasDescriptor(AssetAdministrationShellDescriptor descr) throws AasDescriptorAlreadyExistsException { + decorated.insertAasDescriptor(descr); + } + + @Override + public void replaceAasDescriptor(String aasDescriptorId, AssetAdministrationShellDescriptor descriptor) throws AasDescriptorNotFoundException { + decorated.replaceAasDescriptor(aasDescriptorId, descriptor); + } + + @Override + public void removeAasDescriptor(String aasDescriptorId) throws AasDescriptorNotFoundException { + decorated.removeAasDescriptor(aasDescriptorId); + } + + @Override + public CursorResult> getAllSubmodels(String aasDescriptorId, PaginationInfo pRequest) throws AasDescriptorNotFoundException { + try { + return decorated.getAllSubmodels(aasDescriptorId, pRequest); + } catch (AasDescriptorNotFoundException e) { + try { + return AasRegistryModelMapper.mapEqModel(getDelegatedRegistryApi(aasDescriptorId).getAllSubmodelDescriptorsThroughSuperpath(aasDescriptorId, pRequest.getLimit(), pRequest.getCursor())); + } catch (ApiException e1) { + throw AasRegistryModelMapper.mapApiException(e1, aasDescriptorId); + } + } + } + + @Override + public SubmodelDescriptor getSubmodel(String aasDescriptorId, String submodelId) throws AasDescriptorNotFoundException, SubmodelNotFoundException { + try { + return decorated.getSubmodel(aasDescriptorId, submodelId); + } catch (AasDescriptorNotFoundException e) { + try { + return AasRegistryModelMapper.mapEqModel(getDelegatedRegistryApi(aasDescriptorId).getSubmodelDescriptorByIdThroughSuperpath(aasDescriptorId, submodelId)); + } catch (ApiException e1) { + throw AasRegistryModelMapper.mapApiException(e1, aasDescriptorId, submodelId); + } + } + } + + @Override + public void insertSubmodel(String aasDescriptorId, SubmodelDescriptor submodel) throws AasDescriptorNotFoundException, SubmodelAlreadyExistsException { + decorated.insertSubmodel(aasDescriptorId, submodel); + } + + @Override + public void replaceSubmodel(String aasDescriptorId, String submodelId, SubmodelDescriptor submodel) throws AasDescriptorNotFoundException, SubmodelNotFoundException { + decorated.replaceSubmodel(aasDescriptorId, submodelId, submodel); + } + + @Override + public void removeSubmodel(String aasDescriptorId, String submodelId) throws AasDescriptorNotFoundException, SubmodelNotFoundException { + decorated.removeSubmodel(aasDescriptorId, submodelId); + } + + @Override + public Set clear() { + return decorated.clear(); + } + + @Override + public ShellDescriptorSearchResponse searchAasDescriptors(ShellDescriptorSearchRequest request) { + return decorated.searchAasDescriptors(request); + } + + private RegistryAndDiscoveryInterfaceApi getDelegatedRegistryApi(String aasId) { + String delegationUrl = delegationStrategy.buildDelegatedRegistryUrl(aasId).orElseThrow(() -> new AasDescriptorNotFoundException(aasId)); + return new RegistryAndDiscoveryInterfaceApi(delegationUrl); + } +} diff --git a/basyx.aasregistry/basyx.aasregistry-feature-hierarchy/src/test/java/org/eclipse/digitaltwin/basyx/aasregistry/feature/hierarchy/DummyAasRegistryComponent.java b/basyx.aasregistry/basyx.aasregistry-feature-hierarchy/src/test/java/org/eclipse/digitaltwin/basyx/aasregistry/feature/hierarchy/DummyAasRegistryComponent.java new file mode 100644 index 000000000..e749a2acb --- /dev/null +++ b/basyx.aasregistry/basyx.aasregistry-feature-hierarchy/src/test/java/org/eclipse/digitaltwin/basyx/aasregistry/feature/hierarchy/DummyAasRegistryComponent.java @@ -0,0 +1,43 @@ +/******************************************************************************* + * 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.aasregistry.feature.hierarchy; + +import org.springframework.boot.SpringApplication; +import org.springframework.boot.autoconfigure.SpringBootApplication; + +/** + * Creates and starts the {@link HierarchicalAasRegistryFeature} for tests + * + * @author mateusmolina + * + */ +@SpringBootApplication(scanBasePackages = { "org.eclipse.digitaltwin.basyx.aasregistry.feature.hierarchy", "org.eclipse.digitaltwin.basyx.aasregistry.service.api", "org.eclipse.digitaltwin.basyx.aasregistry.service.events", + "org.eclipse.digitaltwin.basyx.aasregistry.service.configuration", "org.eclipse.digitaltwin.basyx.aasregistry.service.errors", "org.eclipse.digitaltwin.basyx.common.hierarchy" }) +public class DummyAasRegistryComponent { + public static void main(String[] args) { + SpringApplication.run(DummyAasRegistryComponent.class, args); + } +} \ No newline at end of file diff --git a/basyx.aasregistry/basyx.aasregistry-feature-hierarchy/src/test/java/org/eclipse/digitaltwin/basyx/aasregistry/feature/hierarchy/DummyDescriptorFactory.java b/basyx.aasregistry/basyx.aasregistry-feature-hierarchy/src/test/java/org/eclipse/digitaltwin/basyx/aasregistry/feature/hierarchy/DummyDescriptorFactory.java new file mode 100644 index 000000000..512b3217d --- /dev/null +++ b/basyx.aasregistry/basyx.aasregistry-feature-hierarchy/src/test/java/org/eclipse/digitaltwin/basyx/aasregistry/feature/hierarchy/DummyDescriptorFactory.java @@ -0,0 +1,142 @@ +/******************************************************************************* + * 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.aasregistry.feature.hierarchy; + +import java.net.MalformedURLException; +import java.net.URL; +import java.util.List; + +import org.eclipse.digitaltwin.basyx.aasregistry.client.model.AssetAdministrationShellDescriptor; +import org.eclipse.digitaltwin.basyx.aasregistry.client.model.AssetKind; +import org.eclipse.digitaltwin.basyx.aasregistry.client.model.Endpoint; +import org.eclipse.digitaltwin.basyx.aasregistry.client.model.ProtocolInformation; +import org.eclipse.digitaltwin.basyx.aasregistry.client.model.SubmodelDescriptor; +import org.eclipse.digitaltwin.basyx.http.Base64UrlEncodedIdentifier; + +/** + * DummyDescriptorFactory + * + * @author mateusmolina + * + */ +public class DummyDescriptorFactory { + static final String ROOT_ONLY_AASDESCRIPTOR_ID = "AASDESCRIPTOR_ID_ROOTONLY"; + static final String ROOT_ONLY_SMDESCRIPTOR_ID = "SMDESCRIPTOR_ID_ROOTONLY"; + static final String DELEGATED_ONLY_SMDESCRIPTOR_ID = "SMDESCRIPTOR_ID_DELEGATEDONLY"; + + private static final String AAS_INTERFACE = "AAS-3.0"; + private static final String SM_INTERFACE = "SUBMODEL-3.0"; + + private final String delegatedOnlyAasDescriptorId; + private final String repoBaseUrl; + + public DummyDescriptorFactory(String repoBaseUrl, String delegatedAasId) { + this.delegatedOnlyAasDescriptorId = delegatedAasId; + this.repoBaseUrl = repoBaseUrl; + } + + AssetAdministrationShellDescriptor getAasDescriptor_RootOnly() { + return createDummyDescriptor(repoBaseUrl, ROOT_ONLY_AASDESCRIPTOR_ID, buildIdShort(ROOT_ONLY_AASDESCRIPTOR_ID), getSmDescriptor_RootOnly()); + } + + SubmodelDescriptor getSmDescriptor_RootOnly() { + return createDummySubmodelDescriptor(repoBaseUrl, ROOT_ONLY_SMDESCRIPTOR_ID, buildIdShort(ROOT_ONLY_SMDESCRIPTOR_ID)); + } + + AssetAdministrationShellDescriptor getAasDescriptor_DelegatedOnly() { + return createDummyDescriptor(repoBaseUrl, delegatedOnlyAasDescriptorId, buildIdShort(delegatedOnlyAasDescriptorId), getSmDescriptor_DelegatedOnly()); + } + + SubmodelDescriptor getSmDescriptor_DelegatedOnly() { + return createDummySubmodelDescriptor(repoBaseUrl, DELEGATED_ONLY_SMDESCRIPTOR_ID, buildIdShort(DELEGATED_ONLY_SMDESCRIPTOR_ID)); + } + + String getDelegatedOnlyAasDescriptorId() { + return delegatedOnlyAasDescriptorId; + } + + String getRepoBaseUrl() { + return repoBaseUrl; + } + + private static AssetAdministrationShellDescriptor createDummyDescriptor(String baseUrl, String shellId, String shellIdShort, SubmodelDescriptor submodelDescriptor) { + AssetAdministrationShellDescriptor descriptor = new AssetAdministrationShellDescriptor().id(shellId); + descriptor.setIdShort(shellIdShort); + descriptor.setAssetKind(AssetKind.TYPE); + descriptor.setAssetType("TestAsset"); + + setEndpointItem(baseUrl, shellId, descriptor); + descriptor.setGlobalAssetId("DummyGlobalAssetId"); + descriptor.addSubmodelDescriptorsItem(submodelDescriptor); + + return descriptor; + } + + private static SubmodelDescriptor createDummySubmodelDescriptor(String baseUrl, String submodelId, String submodelIdShort) { + SubmodelDescriptor descriptor = new SubmodelDescriptor().id(submodelId); + descriptor.setIdShort(submodelIdShort); + descriptor.setEndpoints(List.of(new Endpoint()._interface(SM_INTERFACE).protocolInformation(createSmProtocolInformation(baseUrl, submodelId)))); + + return descriptor; + } + + private static void setEndpointItem(String baseUrl, String shellId, AssetAdministrationShellDescriptor descriptor) { + ProtocolInformation protocolInformation = createProtocolInformation(baseUrl, shellId); + + Endpoint endpoint = new Endpoint()._interface(AAS_INTERFACE).protocolInformation(protocolInformation); + descriptor.addEndpointsItem(endpoint); + } + + private static ProtocolInformation createProtocolInformation(String baseUrl, String shellId) { + String href = String.format("%s/%s", baseUrl + "/shells", Base64UrlEncodedIdentifier.encodeIdentifier(shellId)); + + ProtocolInformation protocolInformation = new ProtocolInformation().href(href); + protocolInformation.endpointProtocol(getProtocol(href)); + + return protocolInformation; + } + + private static ProtocolInformation createSmProtocolInformation(String baseUrl, String smId) { + String href = String.format("%s/%s", baseUrl + "/submodels", Base64UrlEncodedIdentifier.encodeIdentifier(smId)); + + ProtocolInformation protocolInformation = new ProtocolInformation().href(href); + protocolInformation.endpointProtocol(getProtocol(href)); + + return protocolInformation; + } + + private static String getProtocol(String endpoint) { + try { + return new URL(endpoint).getProtocol(); + } catch (MalformedURLException e) { + throw new RuntimeException(); + } + } + + private static String buildIdShort(String aasId) { + return aasId + "IdShort"; + } +} diff --git a/basyx.aasregistry/basyx.aasregistry-feature-hierarchy/src/test/java/org/eclipse/digitaltwin/basyx/aasregistry/feature/hierarchy/HierarchicalAasRegistryTestSuite.java b/basyx.aasregistry/basyx.aasregistry-feature-hierarchy/src/test/java/org/eclipse/digitaltwin/basyx/aasregistry/feature/hierarchy/HierarchicalAasRegistryTestSuite.java new file mode 100644 index 000000000..99bcb8573 --- /dev/null +++ b/basyx.aasregistry/basyx.aasregistry-feature-hierarchy/src/test/java/org/eclipse/digitaltwin/basyx/aasregistry/feature/hierarchy/HierarchicalAasRegistryTestSuite.java @@ -0,0 +1,156 @@ +/******************************************************************************* + * 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.aasregistry.feature.hierarchy; + +import static org.junit.Assert.*; + +import java.util.Arrays; +import java.util.List; + +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.client.model.GetSubmodelDescriptorsResult; +import org.eclipse.digitaltwin.basyx.aasregistry.client.model.SubmodelDescriptor; +import org.junit.Before; +import org.junit.Test; +import org.springframework.http.HttpStatus; + +/** + * Test for {@link HierarchicalAasRegistryFeature} + * + * @author mateusmolina + * + */ +public abstract class HierarchicalAasRegistryTestSuite { + + protected abstract RegistryAndDiscoveryInterfaceApi getRootRegistryApi(); + protected abstract RegistryAndDiscoveryInterfaceApi getDelegatedRegistryApi(); + protected abstract DummyDescriptorFactory getDescriptorFactory(); + + @Before + public void setUp() throws ApiException { + cleanUpRegistries(); + setupDelegatedRegistry(); + setupRootRegistry(); + } + + @Test + public void getAasDescriptor_InRoot() throws ApiException { + AssetAdministrationShellDescriptor actualDescriptor = getRootRegistryApi().getAssetAdministrationShellDescriptorById(DummyDescriptorFactory.ROOT_ONLY_AASDESCRIPTOR_ID); + + AssetAdministrationShellDescriptor expectedDescriptor = getDescriptorFactory().getAasDescriptor_RootOnly(); + + assertEquals(expectedDescriptor, actualDescriptor); + } + + @Test + public void getAasDescriptor_ThroughDelegated() throws ApiException { + AssetAdministrationShellDescriptor actualDescriptor = getRootRegistryApi().getAssetAdministrationShellDescriptorById(getDescriptorFactory().getDelegatedOnlyAasDescriptorId()); + + AssetAdministrationShellDescriptor expectedDescriptor = getDescriptorFactory().getAasDescriptor_DelegatedOnly(); + + assertEquals(expectedDescriptor, actualDescriptor); + } + + @Test + public void getNonExistingAasDescriptor() { + ApiException exception = assertThrows(ApiException.class, () -> getRootRegistryApi().getAssetAdministrationShellDescriptorById("nonExistingAasDescriptor")); + assertHttpCodeIsNotFound(exception); + } + + @Test + public void getSubmodelDescriptor_InRoot() throws ApiException { + SubmodelDescriptor actualDescriptor = getRootRegistryApi().getSubmodelDescriptorByIdThroughSuperpath(DummyDescriptorFactory.ROOT_ONLY_AASDESCRIPTOR_ID, DummyDescriptorFactory.ROOT_ONLY_SMDESCRIPTOR_ID); + + SubmodelDescriptor expectedDescriptor = getDescriptorFactory().getSmDescriptor_RootOnly(); + + assertEquals(expectedDescriptor, actualDescriptor); + } + + @Test + public void getSubmodelDescriptor_ThroughDelegated() throws ApiException { + SubmodelDescriptor actualDescriptor = getRootRegistryApi().getSubmodelDescriptorByIdThroughSuperpath(getDescriptorFactory().getDelegatedOnlyAasDescriptorId(), DummyDescriptorFactory.DELEGATED_ONLY_SMDESCRIPTOR_ID); + + SubmodelDescriptor expectedDescriptor = getDescriptorFactory().getSmDescriptor_DelegatedOnly(); + + assertEquals(expectedDescriptor, actualDescriptor); + } + + @Test + public void getAllSubmodelDescriptor_InRoot() throws ApiException { + GetSubmodelDescriptorsResult actualDescriptors = getRootRegistryApi().getAllSubmodelDescriptorsThroughSuperpath(DummyDescriptorFactory.ROOT_ONLY_AASDESCRIPTOR_ID, 1, null); + + SubmodelDescriptor expectedDescriptor = getDescriptorFactory().getSmDescriptor_RootOnly(); + + List expectedDescriptors = Arrays.asList(expectedDescriptor); + + assertEquals(expectedDescriptors, actualDescriptors.getResult()); + } + + @Test + public void getAllSubmodelDescriptor_ThroughDelegated() throws ApiException { + getDescriptorFactory(); + GetSubmodelDescriptorsResult actualDescriptors = getRootRegistryApi().getAllSubmodelDescriptorsThroughSuperpath(getDescriptorFactory().getDelegatedOnlyAasDescriptorId(), 1, null); + + SubmodelDescriptor expectedDescriptor = getDescriptorFactory().getSmDescriptor_DelegatedOnly(); + + List expectedDescriptors = Arrays.asList(expectedDescriptor); + + assertEquals(expectedDescriptors, actualDescriptors.getResult()); + } + + @Test + public void getNonExistingSmDescriptor() { + ApiException exception = assertThrows(ApiException.class, () -> getRootRegistryApi().getSubmodelDescriptorByIdThroughSuperpath(DummyDescriptorFactory.ROOT_ONLY_AASDESCRIPTOR_ID, "nonExistingSubmodel")); + assertHttpCodeIsNotFound(exception); + + exception = assertThrows(ApiException.class, () -> getRootRegistryApi().getSubmodelDescriptorByIdThroughSuperpath(getDescriptorFactory().getDelegatedOnlyAasDescriptorId(), "nonExistingSubmodel")); + assertHttpCodeIsNotFound(exception); + + exception = assertThrows(ApiException.class, () -> getRootRegistryApi().getSubmodelDescriptorByIdThroughSuperpath("nonExistingAas", "nonExistingSubmodel")); + assertHttpCodeIsNotFound(exception); + } + + private void setupRootRegistry() throws ApiException { + getRootRegistryApi().postAssetAdministrationShellDescriptor(getDescriptorFactory().getAasDescriptor_RootOnly()); + } + + private void setupDelegatedRegistry() throws ApiException { + AssetAdministrationShellDescriptor descriptor = getDescriptorFactory().getAasDescriptor_DelegatedOnly(); + + getDelegatedRegistryApi().postAssetAdministrationShellDescriptor(descriptor); + } + + private void cleanUpRegistries() throws ApiException { + getRootRegistryApi().deleteAllShellDescriptors(); + getDelegatedRegistryApi().deleteAllShellDescriptors(); + } + + private static void assertHttpCodeIsNotFound(ApiException e) { + assertEquals(HttpStatus.NOT_FOUND.value(), e.getCode()); + } +} diff --git a/basyx.aasregistry/basyx.aasregistry-feature-hierarchy/src/test/java/org/eclipse/digitaltwin/basyx/aasregistry/feature/hierarchy/LocalhostHierarchicalAasRegistryTest.java b/basyx.aasregistry/basyx.aasregistry-feature-hierarchy/src/test/java/org/eclipse/digitaltwin/basyx/aasregistry/feature/hierarchy/LocalhostHierarchicalAasRegistryTest.java new file mode 100644 index 000000000..af32a6ed0 --- /dev/null +++ b/basyx.aasregistry/basyx.aasregistry-feature-hierarchy/src/test/java/org/eclipse/digitaltwin/basyx/aasregistry/feature/hierarchy/LocalhostHierarchicalAasRegistryTest.java @@ -0,0 +1,79 @@ +/******************************************************************************* + * 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.aasregistry.feature.hierarchy; + +import org.eclipse.digitaltwin.basyx.aasregistry.client.api.RegistryAndDiscoveryInterfaceApi; +import org.junit.AfterClass; +import org.junit.BeforeClass; +import org.springframework.boot.SpringApplication; +import org.springframework.context.ConfigurableApplicationContext; + +/** + * LocalhostHierarchicalAasRegistryTest + * + * @author mateusmolina + * + */ +public class LocalhostHierarchicalAasRegistryTest extends HierarchicalAasRegistryTestSuite { + + private static final String ROOT_REGISTRY_URL = "http://localhost:8081"; + private static final String DELEGATED_REGISTRY_URL = "http://localhost:8050"; + + private static final String REPO_BASE_URL = "http://localhost:8080"; + private static final String DELEGATED_AASID = "http://localhost:8050/test/aas"; + + private static final RegistryAndDiscoveryInterfaceApi rootRegistryApi = new RegistryAndDiscoveryInterfaceApi(ROOT_REGISTRY_URL); + private static final RegistryAndDiscoveryInterfaceApi delegatedRegistryApi = new RegistryAndDiscoveryInterfaceApi(DELEGATED_REGISTRY_URL); + private static final DummyDescriptorFactory factory = new DummyDescriptorFactory(REPO_BASE_URL, DELEGATED_AASID); + + private static ConfigurableApplicationContext appContext; + + @BeforeClass + public static void setupRootRegistry() { + appContext = new SpringApplication(DummyAasRegistryComponent.class).run(new String[] {}); + } + + @AfterClass + public static void tearDownRootRegistry() { + appContext.close(); + } + + @Override + protected RegistryAndDiscoveryInterfaceApi getRootRegistryApi() { + return rootRegistryApi; + } + + @Override + protected RegistryAndDiscoveryInterfaceApi getDelegatedRegistryApi() { + return delegatedRegistryApi; + } + + @Override + protected DummyDescriptorFactory getDescriptorFactory() { + return factory; + } + +} diff --git a/basyx.aasregistry/basyx.aasregistry-feature-hierarchy/src/test/resources/application.yml b/basyx.aasregistry/basyx.aasregistry-feature-hierarchy/src/test/resources/application.yml new file mode 100644 index 000000000..32b89c554 --- /dev/null +++ b/basyx.aasregistry/basyx.aasregistry-feature-hierarchy/src/test/resources/application.yml @@ -0,0 +1,47 @@ +--- +events: + sink: log +description: + profiles: https://admin-shell.io/aas/API/3/0/AssetAdministrationShellRegistryServiceSpecification/SSP-001 +springdoc: + api-docs: + path: /api-docs +springfox: + documentation: + enabled: true + # open-api.v3.path: /api-docs +management: + endpoints: + web: + exposure: + include: "health,metrics" +logging: + level: + root: INFO +server: + shutdown: graceful + port: 8081 + error: + whitelabel: + enabled: false +spring: + application: + name: Basyx Aas Registry + jackson: + date-format: org.eclipse.digitaltwin.basyx.aasregistry.service.RFC3339DateFormat + serialization: + WRITE_DATES_AS_TIMESTAMPS: false + profiles: + active: logEvents,inMemoryStorage + +registry: + type: inMemory + +basyx: + cors: + allowed-origins: "*" + allowed-methods: "GET,POST,PATCH,DELETE,PUT,OPTIONS,HEAD" + feature: + hierarchy: + enabled: true + prefix: \ No newline at end of file diff --git a/basyx.aasregistry/basyx.aasregistry-service-release-kafka-mem/pom.xml b/basyx.aasregistry/basyx.aasregistry-service-release-kafka-mem/pom.xml index 10b50cf46..600a7eefe 100644 --- a/basyx.aasregistry/basyx.aasregistry-service-release-kafka-mem/pom.xml +++ b/basyx.aasregistry/basyx.aasregistry-service-release-kafka-mem/pom.xml @@ -32,6 +32,10 @@ org.eclipse.digitaltwin.basyx basyx.aasregistry-feature-authorization + + org.eclipse.digitaltwin.basyx + basyx.aasregistry-feature-hierarchy + org.eclipse.digitaltwin.basyx basyx.aasregistry-service-kafka-events diff --git a/basyx.aasregistry/basyx.aasregistry-service-release-kafka-mongodb/pom.xml b/basyx.aasregistry/basyx.aasregistry-service-release-kafka-mongodb/pom.xml index 5788f88f6..ee69f9c12 100644 --- a/basyx.aasregistry/basyx.aasregistry-service-release-kafka-mongodb/pom.xml +++ b/basyx.aasregistry/basyx.aasregistry-service-release-kafka-mongodb/pom.xml @@ -24,6 +24,10 @@ org.eclipse.digitaltwin.basyx basyx.aasregistry-feature-authorization + + org.eclipse.digitaltwin.basyx + basyx.aasregistry-feature-hierarchy + org.eclipse.digitaltwin.basyx basyx.authorization diff --git a/basyx.aasregistry/basyx.aasregistry-service-release-log-mem/pom.xml b/basyx.aasregistry/basyx.aasregistry-service-release-log-mem/pom.xml index 83a635fad..8fdef0705 100644 --- a/basyx.aasregistry/basyx.aasregistry-service-release-log-mem/pom.xml +++ b/basyx.aasregistry/basyx.aasregistry-service-release-log-mem/pom.xml @@ -25,6 +25,10 @@ org.eclipse.digitaltwin.basyx basyx.aasregistry-feature-authorization + + org.eclipse.digitaltwin.basyx + basyx.aasregistry-feature-hierarchy + org.eclipse.digitaltwin.basyx basyx.authorization diff --git a/basyx.aasregistry/basyx.aasregistry-service-release-log-mongodb/pom.xml b/basyx.aasregistry/basyx.aasregistry-service-release-log-mongodb/pom.xml index 396b026ea..6e2193f42 100644 --- a/basyx.aasregistry/basyx.aasregistry-service-release-log-mongodb/pom.xml +++ b/basyx.aasregistry/basyx.aasregistry-service-release-log-mongodb/pom.xml @@ -29,6 +29,10 @@ org.eclipse.digitaltwin.basyx basyx.aasregistry-feature-authorization + + org.eclipse.digitaltwin.basyx + basyx.aasregistry-feature-hierarchy + org.eclipse.digitaltwin.basyx basyx.aasregistry-service-mongodb-storage diff --git a/basyx.aasregistry/basyx.aasregistry-service/templates/openapi2SpringBoot.mustache b/basyx.aasregistry/basyx.aasregistry-service/templates/openapi2SpringBoot.mustache index 14b449123..72601025b 100644 --- a/basyx.aasregistry/basyx.aasregistry-service/templates/openapi2SpringBoot.mustache +++ b/basyx.aasregistry/basyx.aasregistry-service/templates/openapi2SpringBoot.mustache @@ -15,7 +15,7 @@ import org.springframework.context.annotation.FullyQualifiedAnnotationBeanNameGe nameGenerator = FullyQualifiedAnnotationBeanNameGenerator.class ) @ComponentScan( - basePackages = {"org.eclipse.digitaltwin.basyx.authorization", "org.eclipse.digitaltwin.basyx.authorization.rbac", "org.eclipse.digitaltwin.basyx.aasregistry.feature", "{{basePackage}}", "{{apiPackage}}" , "{{configPackage}}"}, + basePackages = {"org.eclipse.digitaltwin.basyx.authorization", "org.eclipse.digitaltwin.basyx.common.hierarchy", "org.eclipse.digitaltwin.basyx.authorization.rbac", "org.eclipse.digitaltwin.basyx.aasregistry.feature", "{{basePackage}}", "{{apiPackage}}" , "{{configPackage}}"}, nameGenerator = FullyQualifiedAnnotationBeanNameGenerator.class ) public class OpenApiGeneratorApplication { diff --git a/basyx.aasregistry/pom.xml b/basyx.aasregistry/pom.xml index 6cf1072d5..4cfd24aa4 100644 --- a/basyx.aasregistry/pom.xml +++ b/basyx.aasregistry/pom.xml @@ -54,6 +54,8 @@ basyx.aasregistry-service-release-kafka-mem basyx.aasregistry-service-release-kafka-mongodb basyx.aasregistry-feature-authorization + basyx.aasregistry-feature-hierarchy + basyx.aasregistry-feature-hierarchy-example @@ -158,6 +160,11 @@ basyx.aasregistry-feature-authorization ${project.version} + + org.eclipse.digitaltwin.basyx + basyx.aasregistry-feature-hierarchy + ${project.version} + org.eclipse.digitaltwin.basyx basyx.aasregistry-service-basetests diff --git a/basyx.common/basyx.hierarchy/pom.xml b/basyx.common/basyx.hierarchy/pom.xml new file mode 100644 index 000000000..e04e5a17d --- /dev/null +++ b/basyx.common/basyx.hierarchy/pom.xml @@ -0,0 +1,30 @@ + + 4.0.0 + + org.eclipse.digitaltwin.basyx + basyx.common + ${revision} + + basyx.hierarchy + BaSyx Hierarchy Core + BaSyx Hierarchy Core + + + + org.springframework + spring-context + + + org.springframework.boot + spring-boot-starter + + + junit + junit + test + + + + \ No newline at end of file diff --git a/basyx.common/basyx.hierarchy/src/main/java/org/eclipse/digitaltwin/basyx/common/hierarchy/CommonHierarchyProperties.java b/basyx.common/basyx.hierarchy/src/main/java/org/eclipse/digitaltwin/basyx/common/hierarchy/CommonHierarchyProperties.java new file mode 100644 index 000000000..5ad7a8fd5 --- /dev/null +++ b/basyx.common/basyx.hierarchy/src/main/java/org/eclipse/digitaltwin/basyx/common/hierarchy/CommonHierarchyProperties.java @@ -0,0 +1,11 @@ +package org.eclipse.digitaltwin.basyx.common.hierarchy; + +public final class CommonHierarchyProperties { + + private CommonHierarchyProperties() { + } + + public static final String HIERARCHY_FEATURE_NAME = "basyx.feature.hierarchy"; + public static final String HIERARCHY_FEATURE_ENABLED = "basyx.feature.hierarchy.enabled"; + public static final String HIERARCHY_FEATURE_DELEGATION_PREFIX = "basyx.feature.hierarchy.prefix"; +} diff --git a/basyx.common/basyx.hierarchy/src/main/java/org/eclipse/digitaltwin/basyx/common/hierarchy/delegation/DelegationStrategy.java b/basyx.common/basyx.hierarchy/src/main/java/org/eclipse/digitaltwin/basyx/common/hierarchy/delegation/DelegationStrategy.java new file mode 100644 index 000000000..41be5de24 --- /dev/null +++ b/basyx.common/basyx.hierarchy/src/main/java/org/eclipse/digitaltwin/basyx/common/hierarchy/delegation/DelegationStrategy.java @@ -0,0 +1,48 @@ +/******************************************************************************* + * 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.common.hierarchy.delegation; + +import java.util.Optional; + +/** + * DelegationStrategy + * + * @author mateusmolina + * + */ +@FunctionalInterface +public interface DelegationStrategy { + /** + * Builds the URL for the delegation. + * + * @param identifier + * The ID of the AAS for which the delegated registry URL is to be + * built. + * @return An Optional containing the delegated URL if it can be + * constructed, or an empty Optional otherwise. + */ + public Optional buildDelegatedRegistryUrl(String identifier); +} diff --git a/basyx.common/basyx.hierarchy/src/main/java/org/eclipse/digitaltwin/basyx/common/hierarchy/delegation/PrefixDelegationStrategy.java b/basyx.common/basyx.hierarchy/src/main/java/org/eclipse/digitaltwin/basyx/common/hierarchy/delegation/PrefixDelegationStrategy.java new file mode 100644 index 000000000..14fc49134 --- /dev/null +++ b/basyx.common/basyx.hierarchy/src/main/java/org/eclipse/digitaltwin/basyx/common/hierarchy/delegation/PrefixDelegationStrategy.java @@ -0,0 +1,84 @@ +/******************************************************************************* + * 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.common.hierarchy.delegation; + +import java.net.MalformedURLException; +import java.net.URL; +import java.util.Optional; + +import org.eclipse.digitaltwin.basyx.common.hierarchy.CommonHierarchyProperties; +import org.springframework.beans.factory.annotation.Value; +import org.springframework.stereotype.Component; + +/** + * Generates delegation URL based on value of the prefix attribute. Default + * value is 'registry'. + * + * @author mateusmolina + * + */ +@Component +public class PrefixDelegationStrategy implements DelegationStrategy { + private final String prefix; + + public PrefixDelegationStrategy(@Value("${" + CommonHierarchyProperties.HIERARCHY_FEATURE_DELEGATION_PREFIX + ":registry}") String prefix) { + this.prefix = prefix; + } + + @Override + public Optional buildDelegatedRegistryUrl(String identifier) { + return Optional.ofNullable(identifier).map(this::extractDelegationUrl); + } + + private String extractDelegationUrl(String identifier) { + URL url; + + try { + url = new URL(identifier); + } catch (MalformedURLException e) { + return null; + } + + String host = url.getHost(); + int port = url.getPort(); + String protocol = url.getProtocol(); + + StringBuilder registryUrl = new StringBuilder(); + + registryUrl.append(protocol).append("://"); + + if (prefix != null && !prefix.isBlank()) + registryUrl.append(prefix).append("."); + + registryUrl.append(host); + + if (port != -1) { + registryUrl.append(":").append(port); + } + + return registryUrl.toString(); + } +} \ No newline at end of file diff --git a/basyx.common/basyx.hierarchy/src/test/java/org/eclipse/digitaltwin/basyx/common/hierarchy/delegation/PrefixDelegationStrategyTest.java b/basyx.common/basyx.hierarchy/src/test/java/org/eclipse/digitaltwin/basyx/common/hierarchy/delegation/PrefixDelegationStrategyTest.java new file mode 100644 index 000000000..9bb11ec75 --- /dev/null +++ b/basyx.common/basyx.hierarchy/src/test/java/org/eclipse/digitaltwin/basyx/common/hierarchy/delegation/PrefixDelegationStrategyTest.java @@ -0,0 +1,93 @@ +/******************************************************************************* + * 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.common.hierarchy.delegation; + +import static org.junit.Assert.assertEquals; +import static org.junit.Assert.assertTrue; + +import org.junit.Before; +import org.junit.Test; + +/** + * PrefixDelegationStrategyTest + * + * @author mateusmolina + * + */ + +public class PrefixDelegationStrategyTest { + + private PrefixDelegationStrategy prefixDelegationStrategy; + + @Before + public void setUp() { + prefixDelegationStrategy = new PrefixDelegationStrategy("registry"); + } + + @Test + public void testBuildDelegatedRegistryUrl_WithPort() { + String aasId = "http://fraunhofer:8042/example/aas"; + String expectedUrl = "http://registry.fraunhofer:8042"; + String actualUrl = prefixDelegationStrategy.buildDelegatedRegistryUrl(aasId).get(); + assertEquals(expectedUrl, actualUrl); + } + + @Test + public void testBuildDelegatedRegistryUrl_WithoutPort() { + String aasId = "http://fraunhofer/example/aas"; + String expectedUrl = "http://registry.fraunhofer"; + String actualUrl = prefixDelegationStrategy.buildDelegatedRegistryUrl(aasId).get(); + assertEquals(expectedUrl, actualUrl); + } + + @Test + public void testBuildDelegatedRegistryUrl_WithTLD() { + String aasId = "http://fraunhofer.com:8080/example/aas"; + String expectedUrl = "http://registry.fraunhofer.com:8080"; + String actualUrl = prefixDelegationStrategy.buildDelegatedRegistryUrl(aasId).get(); + assertEquals(expectedUrl, actualUrl); + } + + @Test + public void testBuildDelegatedRegistryUrl_InvalidUrl() { + String aasId = "invalid_url"; + assertTrue(prefixDelegationStrategy.buildDelegatedRegistryUrl(aasId).isEmpty()); + } + + @Test + public void testBuildDelegatedRegistryUrl_WithBlankPrefix() { + PrefixDelegationStrategy blankPrefixDelegationStrategy = new PrefixDelegationStrategy(""); + String aasId = "http://fraunhofer.com:8080/example/aas"; + String expectedUrl = "http://fraunhofer.com:8080"; + String actualUrl = blankPrefixDelegationStrategy.buildDelegatedRegistryUrl(aasId).get(); + assertEquals(expectedUrl, actualUrl); + } + + @Test + public void testBuildDelegatedRegistryUrl_NullAasId() { + assertTrue(prefixDelegationStrategy.buildDelegatedRegistryUrl(null).isEmpty()); + } +} \ No newline at end of file diff --git a/basyx.common/pom.xml b/basyx.common/pom.xml index fe6807eaa..ee719ca26 100644 --- a/basyx.common/pom.xml +++ b/basyx.common/pom.xml @@ -19,6 +19,7 @@ basyx.mqttcore basyx.mongocore basyx.authorization + basyx.hierarchy basyx.client basyx.backend.inmemory.core basyx.filerepository-backend diff --git a/basyx.submodelregistry/basyx.submodelregistry-feature-hierarchy-example/Readme.md b/basyx.submodelregistry/basyx.submodelregistry-feature-hierarchy-example/Readme.md new file mode 100644 index 000000000..a580a7cc0 --- /dev/null +++ b/basyx.submodelregistry/basyx.submodelregistry-feature-hierarchy-example/Readme.md @@ -0,0 +1,42 @@ +# Submodel Registry - Hierarchy - Example + +This example showcases the working principle of the hierarchical registries feature. + +## Scenario description + +```mermaid +sequenceDiagram + actor Client + participant Root as Root Registry + participant Delegated as Delegated Registry + + Client ->> Root: Request resolution for Submodel-ID "http://delegated-submodel-registry:8080/test/submodel" + activate Root + + Root ->> Root: Check local records + alt Not found locally + Root ->> Root: Determine registry based on URI prefix + Root ->> Delegated: Resolve Submodel-ID "http://delegated-submodel-registry:8080/test/submodel" at registry.delegated-submodel-registry:8080 + activate Delegated + Delegated ->> Delegated: Resolve Submodel- + Delegated ->> Root: Resolution result + deactivate Delegated + end + + Root ->> Client: Return resolution result + deactivate Root +``` + +## Running the scenario + +In order to run the example, please make sure that all submodel registries maven modules are correctly installed in your local Maven repository. + +1. Generate the Docker image: `mvn clean install -Ddocker.namespace=submodel-registry-test` + +2. Run the docker compose: `docker compose up` + +Two containers should start: (1) one for the root Submodel Registry - to which the http request are going to be made; (2) one for the delegated Submodel Registry - to which requests may be delegated to. + +They are visibile within the bridged Docker network as (1) submodel-registry-root:8080 and (2) registry.delegated-submodel-registry:8080 + +3. Run the scenario [HierarchicalSubmodelRegistryIT](/src/test/java/org/eclipse/digitaltwin/basyx/submodelregistry/feature/hierarchy/example/HierachicalSubmodelRegistryIT.java) diff --git a/basyx.submodelregistry/basyx.submodelregistry-feature-hierarchy-example/docker-compose.yml b/basyx.submodelregistry/basyx.submodelregistry-feature-hierarchy-example/docker-compose.yml new file mode 100644 index 000000000..03452b721 --- /dev/null +++ b/basyx.submodelregistry/basyx.submodelregistry-feature-hierarchy-example/docker-compose.yml @@ -0,0 +1,29 @@ +version: "3.9" +services: + + submodel-registry-root: + image: submodel-registry-test/submodel-registry-feature-hierarchy-example:2.0.0-SNAPSHOT + container_name: submodel-registry-root + ports: + - "8051:8080" + environment: + SERVER_SERVLET_CONTEXT_PATH: / + networks: + - basyx-submodelregistry-feature-hierarchy-example + + registry.delegated-submodel-registry: + image: eclipsebasyx/submodel-registry-log-mem:2.0.0-SNAPSHOT + container_name: registry.delegated-submodel-registry + ports: + - "8052:8080" + environment: + SERVER_SERVLET_CONTEXT_PATH: / + volumes: + - ./submodel-registry-delegated.yml:/workspace/config/application.yml + networks: + - basyx-submodelregistry-feature-hierarchy-example + +networks: + basyx-submodelregistry-feature-hierarchy-example: + name: basyx-submodelregistry-feature-hierarchy-example + driver: bridge diff --git a/basyx.submodelregistry/basyx.submodelregistry-feature-hierarchy-example/pom.xml b/basyx.submodelregistry/basyx.submodelregistry-feature-hierarchy-example/pom.xml new file mode 100644 index 000000000..32c3b8e73 --- /dev/null +++ b/basyx.submodelregistry/basyx.submodelregistry-feature-hierarchy-example/pom.xml @@ -0,0 +1,36 @@ + + 4.0.0 + + + org.eclipse.digitaltwin.basyx + basyx.submodelregistry + ${revision} + + + basyx.submodelregistry-feature-hierarchy-example + BaSyx Submodel Registry Feature Hierarchy Example + BaSyx Submodel Registry Feature Hierarchy Example + + + 2020.0.4 + + org.eclipse.digitaltwin.basyx.submodelregistry.service.OpenApiGeneratorApplication + submodel-registry-feature-hierarchy-example + + + + + org.eclipse.digitaltwin.basyx + basyx.submodelregistry-service-release-log-mem + ${revision} + + + org.eclipse.digitaltwin.basyx + basyx.submodelregistry-feature-hierarchy + tests + test + + + \ No newline at end of file diff --git a/basyx.submodelregistry/basyx.submodelregistry-feature-hierarchy-example/src/main/docker/Dockerfile b/basyx.submodelregistry/basyx.submodelregistry-feature-hierarchy-example/src/main/docker/Dockerfile new file mode 100644 index 000000000..d0f923d2b --- /dev/null +++ b/basyx.submodelregistry/basyx.submodelregistry-feature-hierarchy-example/src/main/docker/Dockerfile @@ -0,0 +1,21 @@ +FROM eclipse-temurin:17 as builder +COPY maven/${project.build.finalName}.jar ./ +RUN java -Djarmode=layertools -jar ${project.build.finalName}.jar extract + +FROM eclipse-temurin:17 +RUN mkdir /workspace +WORKDIR /workspace +COPY --from=builder dependencies/ ./ +COPY --from=builder snapshot-dependencies/ ./ +RUN true +COPY --from=builder spring-boot-loader/ ./ +COPY --from=builder application/ ./ +ENV SPRING_PROFILES_ACTIVE=logEvents,inMemoryStorage,hierarchy +ARG PORT=8080 +ENV SERVER_PORT=${PORT} +ARG CONTEXT_PATH=/ +ENV SERVER_SERVLET_CONTEXT_PATH=${CONTEXT_PATH} +EXPOSE ${SERVER_PORT} +HEALTHCHECK --interval=30s --timeout=3s --retries=3 --start-period=15s CMD curl --fail http://localhost:${SERVER_PORT}${SERVER_SERVLET_CONTEXT_PATH%/}/actuator/health || exit 1 +ENTRYPOINT ["java", "-Djava.security.egd=file:/dev/.urandom", "org.springframework.boot.loader.launch.JarLauncher"] + diff --git a/basyx.submodelregistry/basyx.submodelregistry-feature-hierarchy-example/src/main/resources/application-hierarchy.yml b/basyx.submodelregistry/basyx.submodelregistry-feature-hierarchy-example/src/main/resources/application-hierarchy.yml new file mode 100644 index 000000000..2c39855f5 --- /dev/null +++ b/basyx.submodelregistry/basyx.submodelregistry-feature-hierarchy-example/src/main/resources/application-hierarchy.yml @@ -0,0 +1,9 @@ +--- +basyx: + cors: + allowed-origins: "*" + allowed-methods: "GET,POST,PATCH,DELETE,PUT,OPTIONS,HEAD" + feature: + hierarchy: + enabled: true + \ No newline at end of file diff --git a/basyx.submodelregistry/basyx.submodelregistry-feature-hierarchy-example/src/test/java/org/eclipse/digitaltwin/basyx/submodelregistry/feature/hierarchy/example/HierachicalSubmodelRegistryIT.java b/basyx.submodelregistry/basyx.submodelregistry-feature-hierarchy-example/src/test/java/org/eclipse/digitaltwin/basyx/submodelregistry/feature/hierarchy/example/HierachicalSubmodelRegistryIT.java new file mode 100644 index 000000000..0134fada4 --- /dev/null +++ b/basyx.submodelregistry/basyx.submodelregistry-feature-hierarchy-example/src/test/java/org/eclipse/digitaltwin/basyx/submodelregistry/feature/hierarchy/example/HierachicalSubmodelRegistryIT.java @@ -0,0 +1,66 @@ +/******************************************************************************* + * 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.submodelregistry.feature.hierarchy.example; + +import org.eclipse.digitaltwin.basyx.submodelregistry.client.api.SubmodelRegistryApi; +import org.eclipse.digitaltwin.basyx.submodelregistry.feature.hierarchy.DummyDescriptorFactory; +import org.eclipse.digitaltwin.basyx.submodelregistry.feature.hierarchy.HierarchicalSubmodelRegistryTestSuite; + +/** + * HierachicalSubmodelRegistryIT + * + * @author mateusmolina + * + */ +public class HierachicalSubmodelRegistryIT extends HierarchicalSubmodelRegistryTestSuite { + + private static final String ROOT_REGISTRY_URL = "http://localhost:8051"; + private static final String DELEGATED_REGISTRY_URL = "http://localhost:8052"; + + private static final String REPO_BASE_URL = "http://localhost:8080"; + private static final String DELEGATED_SMID = "http://delegated-submodel-registry:8080/test/submodel"; + + private static final DummyDescriptorFactory factory = new DummyDescriptorFactory(REPO_BASE_URL, DELEGATED_SMID); + + private static final SubmodelRegistryApi rootRegistryApi = new SubmodelRegistryApi(ROOT_REGISTRY_URL); + private static final SubmodelRegistryApi delegatedRegistryApi = new SubmodelRegistryApi(DELEGATED_REGISTRY_URL); + + @Override + protected SubmodelRegistryApi getRootRegistryApi() { + return rootRegistryApi; + } + + @Override + protected SubmodelRegistryApi getDelegatedRegistryApi() { + return delegatedRegistryApi; + } + + @Override + protected DummyDescriptorFactory getDescriptorFactory() { + return factory; + } + +} diff --git a/basyx.submodelregistry/basyx.submodelregistry-feature-hierarchy-example/submodel-registry-delegated.yml b/basyx.submodelregistry/basyx.submodelregistry-feature-hierarchy-example/submodel-registry-delegated.yml new file mode 100644 index 000000000..eb3b0feb3 --- /dev/null +++ b/basyx.submodelregistry/basyx.submodelregistry-feature-hierarchy-example/submodel-registry-delegated.yml @@ -0,0 +1,5 @@ +--- +basyx: + cors: + allowed-origins: "*" + allowed-methods: "GET,POST,PATCH,DELETE,PUT,OPTIONS,HEAD" \ No newline at end of file diff --git a/basyx.submodelregistry/basyx.submodelregistry-feature-hierarchy/Readme.md b/basyx.submodelregistry/basyx.submodelregistry-feature-hierarchy/Readme.md new file mode 100644 index 000000000..7a856df27 --- /dev/null +++ b/basyx.submodelregistry/basyx.submodelregistry-feature-hierarchy/Readme.md @@ -0,0 +1,31 @@ +# Submodel Registry - Hierarchy + +The Hierarchical SubmodelRegistry Feature enhances the availability of data across multiple registries by allowing retrieval requests to be delegated to another SubmodelRegistry. + +This feature allows the creation of a hierarchical structure of registries, where a registry can delegate retrieval requests to another registry when a given descriptor is not found in its storage. + +## Configuration + +### Enabling the Feature + +To enable the Hierarchical SubmodelRegistry Feature, add the following property to your Spring application's configuration: + +```properties +basyx.feature.hierarchy.enabled=true +``` + +### Delegation Strategy + +Currently, only one delegation strategy is implemented: + +#### Prefix Delegation Strategy + +Delegates requests based on the `submodelDescriptorId` value. If the ID is an URL, a prefix (defaut `registry`) is appended to the URL and used as delegation endpoint. + +The prefix can be configured by the property `basyx.feature.hierarchy.prefix`. Please refer to the example below: + +```properties +basyx.feature.hierarchy.prefix=registry +``` + +If this property is left with an empty string, no prefix is appended to the URL contained in the `submodelDecriptorId`. diff --git a/basyx.submodelregistry/basyx.submodelregistry-feature-hierarchy/pom.xml b/basyx.submodelregistry/basyx.submodelregistry-feature-hierarchy/pom.xml new file mode 100644 index 000000000..106f97287 --- /dev/null +++ b/basyx.submodelregistry/basyx.submodelregistry-feature-hierarchy/pom.xml @@ -0,0 +1,38 @@ + + 4.0.0 + + + org.eclipse.digitaltwin.basyx + basyx.submodelregistry + ${revision} + + + basyx.submodelregistry-feature-hierarchy + BaSyx Submodel Registry feature-hierarchy + + + + org.eclipse.digitaltwin.basyx + basyx.hierarchy + + + org.eclipse.digitaltwin.basyx + basyx.submodelregistry-service + + + org.eclipse.digitaltwin.basyx + basyx.submodelregistry-service-basemodel + + + org.eclipse.digitaltwin.basyx + basyx.submodelregistry-client-native + + + org.eclipse.digitaltwin.basyx + basyx.submodelregistry-service-inmemory-storage + test + + + \ No newline at end of file diff --git a/basyx.submodelregistry/basyx.submodelregistry-feature-hierarchy/src/main/java/org/eclipse/digitaltwin/basyx/submodelregistry/feature/hierarchy/HierarchicalSubmodelRegistryFeature.java b/basyx.submodelregistry/basyx.submodelregistry-feature-hierarchy/src/main/java/org/eclipse/digitaltwin/basyx/submodelregistry/feature/hierarchy/HierarchicalSubmodelRegistryFeature.java new file mode 100644 index 000000000..095525019 --- /dev/null +++ b/basyx.submodelregistry/basyx.submodelregistry-feature-hierarchy/src/main/java/org/eclipse/digitaltwin/basyx/submodelregistry/feature/hierarchy/HierarchicalSubmodelRegistryFeature.java @@ -0,0 +1,75 @@ +/******************************************************************************* + * 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.submodelregistry.feature.hierarchy; + +import org.eclipse.digitaltwin.basyx.common.hierarchy.CommonHierarchyProperties; +import org.eclipse.digitaltwin.basyx.common.hierarchy.delegation.DelegationStrategy; +import org.eclipse.digitaltwin.basyx.submodelregistry.service.storage.SubmodelRegistryStorage; +import org.eclipse.digitaltwin.basyx.submodelregistry.service.storage.SubmodelRegistryStorageFeature; +import org.springframework.beans.factory.annotation.Value; +import org.springframework.boot.autoconfigure.condition.ConditionalOnExpression; +import org.springframework.core.annotation.Order; +import org.springframework.stereotype.Component; + +/** + * Hierarchical {@link SubmodelRegistryStorage} feature + * + * When this feature is enabled, retrieval requests will be delegated to the + * next SubmodelRegistry. + * + * The next SubmodelRegistry is selected via a {@link DelegationStrategy} + * + * @author mateusmolina + */ +@Component +@ConditionalOnExpression("${" + CommonHierarchyProperties.HIERARCHY_FEATURE_ENABLED + ":false}") +@Order(1) +public class HierarchicalSubmodelRegistryFeature implements SubmodelRegistryStorageFeature { + @Value("${" + CommonHierarchyProperties.HIERARCHY_FEATURE_ENABLED + "}") + private boolean enabled; + + private DelegationStrategy delegationStrategy; + + public HierarchicalSubmodelRegistryFeature(DelegationStrategy delegationStrategy) { + this.delegationStrategy = delegationStrategy; + } + + @Override + public SubmodelRegistryStorage decorate(SubmodelRegistryStorage storage) { + return new HierarchicalSubmodelRegistryStorage(storage, delegationStrategy); + } + + @Override + public String getName() { + return "SubmodelRegistry Hierarchy"; + } + + @Override + public boolean isEnabled() { + return enabled; + } + +} \ No newline at end of file diff --git a/basyx.submodelregistry/basyx.submodelregistry-feature-hierarchy/src/main/java/org/eclipse/digitaltwin/basyx/submodelregistry/feature/hierarchy/HierarchicalSubmodelRegistryStorage.java b/basyx.submodelregistry/basyx.submodelregistry-feature-hierarchy/src/main/java/org/eclipse/digitaltwin/basyx/submodelregistry/feature/hierarchy/HierarchicalSubmodelRegistryStorage.java new file mode 100644 index 000000000..6ecb0ade8 --- /dev/null +++ b/basyx.submodelregistry/basyx.submodelregistry-feature-hierarchy/src/main/java/org/eclipse/digitaltwin/basyx/submodelregistry/feature/hierarchy/HierarchicalSubmodelRegistryStorage.java @@ -0,0 +1,103 @@ +/******************************************************************************* + * 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.submodelregistry.feature.hierarchy; + +import java.util.List; +import java.util.Set; + +import org.eclipse.digitaltwin.basyx.common.hierarchy.delegation.DelegationStrategy; +import org.eclipse.digitaltwin.basyx.core.pagination.CursorResult; +import org.eclipse.digitaltwin.basyx.core.pagination.PaginationInfo; +import org.eclipse.digitaltwin.basyx.submodelregistry.client.ApiException; +import org.eclipse.digitaltwin.basyx.submodelregistry.client.api.SubmodelRegistryApi; +import org.eclipse.digitaltwin.basyx.submodelregistry.model.SubmodelDescriptor; +import org.eclipse.digitaltwin.basyx.submodelregistry.service.errors.SubmodelAlreadyExistsException; +import org.eclipse.digitaltwin.basyx.submodelregistry.service.errors.SubmodelNotFoundException; +import org.eclipse.digitaltwin.basyx.submodelregistry.service.storage.SubmodelRegistryStorage; + +import com.fasterxml.jackson.databind.ObjectMapper; + +/** + * Decorator for Hierarchical {@link AasRegistryStorage} + * + * @author mateusmolina + */ +public class HierarchicalSubmodelRegistryStorage implements SubmodelRegistryStorage { + + private final SubmodelRegistryStorage decorated; + + private final DelegationStrategy delegationStrategy; + + private final SubmodelRegistryModelMapper mapper = new SubmodelRegistryModelMapper(new ObjectMapper()); + + public HierarchicalSubmodelRegistryStorage(SubmodelRegistryStorage decorated, DelegationStrategy delegationStrategy) { + this.decorated = decorated; + this.delegationStrategy = delegationStrategy; + } + + @Override + public Set clear() { + return decorated.clear(); + } + + @Override + public CursorResult> getAllSubmodelDescriptors(PaginationInfo pRequest) { + return decorated.getAllSubmodelDescriptors(pRequest); + } + + @Override + public SubmodelDescriptor getSubmodelDescriptor(String submodelId) throws SubmodelNotFoundException { + try { + return decorated.getSubmodelDescriptor(submodelId); + } catch (SubmodelNotFoundException e) { + try { + return mapper.mapEqModel(getDelegatedRegistryApi(submodelId).getSubmodelDescriptorById(submodelId)); + } catch (ApiException e1) { + throw SubmodelRegistryModelMapper.mapApiException(e1, submodelId); + } + } + } + + @Override + public void insertSubmodelDescriptor(SubmodelDescriptor descr) throws SubmodelAlreadyExistsException { + decorated.insertSubmodelDescriptor(descr); + } + + @Override + public void replaceSubmodelDescriptor(String submodelId, SubmodelDescriptor descr) throws SubmodelNotFoundException { + decorated.replaceSubmodelDescriptor(submodelId, descr); + } + + @Override + public void removeSubmodelDescriptor(String submodelId) throws SubmodelNotFoundException { + decorated.removeSubmodelDescriptor(submodelId); + } + + private SubmodelRegistryApi getDelegatedRegistryApi(String smId) { + String delegationUrl = delegationStrategy.buildDelegatedRegistryUrl(smId).orElseThrow(() -> new SubmodelNotFoundException(smId)); + return new SubmodelRegistryApi(delegationUrl); + } +} diff --git a/basyx.submodelregistry/basyx.submodelregistry-feature-hierarchy/src/main/java/org/eclipse/digitaltwin/basyx/submodelregistry/feature/hierarchy/SubmodelRegistryModelMapper.java b/basyx.submodelregistry/basyx.submodelregistry-feature-hierarchy/src/main/java/org/eclipse/digitaltwin/basyx/submodelregistry/feature/hierarchy/SubmodelRegistryModelMapper.java new file mode 100644 index 000000000..2a48f99c7 --- /dev/null +++ b/basyx.submodelregistry/basyx.submodelregistry-feature-hierarchy/src/main/java/org/eclipse/digitaltwin/basyx/submodelregistry/feature/hierarchy/SubmodelRegistryModelMapper.java @@ -0,0 +1,77 @@ +/******************************************************************************* + * 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.submodelregistry.feature.hierarchy; + +import java.util.List; + +import org.eclipse.digitaltwin.basyx.core.pagination.CursorResult; +import org.eclipse.digitaltwin.basyx.submodelregistry.client.ApiException; +import org.eclipse.digitaltwin.basyx.submodelregistry.client.model.GetSubmodelDescriptorsResult; +import org.eclipse.digitaltwin.basyx.submodelregistry.model.SubmodelDescriptor; +import org.eclipse.digitaltwin.basyx.submodelregistry.service.errors.SubmodelNotFoundException; +import org.springframework.http.HttpStatus; +import org.springframework.http.HttpStatusCode; + +import com.fasterxml.jackson.core.type.TypeReference; +import com.fasterxml.jackson.databind.ObjectMapper; + +/** + * Handles internal model mapping for + * {@link HierarchicalsubmodelRegistryStorage} + * + * @author mateusmolina + * + */ +final class SubmodelRegistryModelMapper { + + private final ObjectMapper objectMapper; + + SubmodelRegistryModelMapper(ObjectMapper objectMapper) { + this.objectMapper = objectMapper; + } + + SubmodelDescriptor mapEqModel(org.eclipse.digitaltwin.basyx.submodelregistry.client.model.SubmodelDescriptor submodelRegistryDescriptor) { + return objectMapper.convertValue(submodelRegistryDescriptor, SubmodelDescriptor.class); + } + + org.eclipse.digitaltwin.basyx.submodelregistry.client.model.SubmodelDescriptor mapEqModel(SubmodelDescriptor smRegistryDescriptor) { + return objectMapper.convertValue(smRegistryDescriptor, org.eclipse.digitaltwin.basyx.submodelregistry.client.model.SubmodelDescriptor.class); + } + + CursorResult> mapEqModel(GetSubmodelDescriptorsResult descriptorResult) { + List submodelDescs = objectMapper.convertValue(descriptorResult.getResult(), new TypeReference>() { + }); + return new CursorResult<>(descriptorResult.getPagingMetadata().getCursor(), submodelDescs); + } + + static RuntimeException mapApiException(ApiException e, String submodelDescriptorId) { + if (HttpStatusCode.valueOf(e.getCode()).equals(HttpStatus.NOT_FOUND)) + return new SubmodelNotFoundException(submodelDescriptorId); + + return new RuntimeException(e); + } + +} diff --git a/basyx.submodelregistry/basyx.submodelregistry-feature-hierarchy/src/test/java/org/eclipse/digitaltwin/basyx/submodelregistry/feature/hierarchy/DummyDescriptorFactory.java b/basyx.submodelregistry/basyx.submodelregistry-feature-hierarchy/src/test/java/org/eclipse/digitaltwin/basyx/submodelregistry/feature/hierarchy/DummyDescriptorFactory.java new file mode 100644 index 000000000..222b9323c --- /dev/null +++ b/basyx.submodelregistry/basyx.submodelregistry-feature-hierarchy/src/test/java/org/eclipse/digitaltwin/basyx/submodelregistry/feature/hierarchy/DummyDescriptorFactory.java @@ -0,0 +1,100 @@ +/******************************************************************************* + * 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.submodelregistry.feature.hierarchy; + +import java.net.MalformedURLException; +import java.net.URL; +import java.util.List; + +import org.eclipse.digitaltwin.basyx.http.Base64UrlEncodedIdentifier; +import org.eclipse.digitaltwin.basyx.submodelregistry.client.model.Endpoint; +import org.eclipse.digitaltwin.basyx.submodelregistry.client.model.ProtocolInformation; +import org.eclipse.digitaltwin.basyx.submodelregistry.client.model.SubmodelDescriptor; + +/** + * DummyDescriptorFactory + * + * @author mateusmolina + * + */ +public class DummyDescriptorFactory { + static final String ROOT_ONLY_SM_ID = "SMDESCRIPTOR_ID_ROOTONLY"; + + private static final String SUBMODEL_INTERFACE = "SUBMODEL-3.0"; + + private final String repoBaseUrl; + private final String delegatedOnlySmId; + + public DummyDescriptorFactory(String repoBaseUrl, String delegatedSmId) { + this.repoBaseUrl = repoBaseUrl; + this.delegatedOnlySmId = delegatedSmId; + } + + SubmodelDescriptor getRootOnlySmDescriptor() { + return createDummySubmodelDescriptor(repoBaseUrl, ROOT_ONLY_SM_ID, buildIdShort(ROOT_ONLY_SM_ID)); + } + + SubmodelDescriptor getDelegatedOnlySmDescriptor() { + return createDummySubmodelDescriptor(repoBaseUrl, delegatedOnlySmId, buildIdShort(delegatedOnlySmId)); + } + + String getDelegatedOnlySmId() { + return delegatedOnlySmId; + } + + String getRepoBaseUrl() { + return repoBaseUrl; + } + + private static SubmodelDescriptor createDummySubmodelDescriptor(String baseUrl, String submodelId, String submodelIdShort) { + SubmodelDescriptor descriptor = new SubmodelDescriptor().id(submodelId); + descriptor.setIdShort(submodelIdShort); + descriptor.setEndpoints(List.of(new Endpoint()._interface(SUBMODEL_INTERFACE).protocolInformation(createSmProtocolInformation(baseUrl, submodelId)))); + + return descriptor; + } + + private static ProtocolInformation createSmProtocolInformation(String baseUrl, String smId) { + String href = String.format("%s/%s", baseUrl + "/submodels", Base64UrlEncodedIdentifier.encodeIdentifier(smId)); + + ProtocolInformation protocolInformation = new ProtocolInformation().href(href); + protocolInformation.endpointProtocol(getProtocol(href)); + + return protocolInformation; + } + + private static String getProtocol(String endpoint) { + try { + return new URL(endpoint).getProtocol(); + } catch (MalformedURLException e) { + throw new RuntimeException(); + } + } + + private static String buildIdShort(String smId) { + return smId + "IdShort"; + } +} diff --git a/basyx.submodelregistry/basyx.submodelregistry-feature-hierarchy/src/test/java/org/eclipse/digitaltwin/basyx/submodelregistry/feature/hierarchy/DummySubmodelRegistryComponent.java b/basyx.submodelregistry/basyx.submodelregistry-feature-hierarchy/src/test/java/org/eclipse/digitaltwin/basyx/submodelregistry/feature/hierarchy/DummySubmodelRegistryComponent.java new file mode 100644 index 000000000..355162dd9 --- /dev/null +++ b/basyx.submodelregistry/basyx.submodelregistry-feature-hierarchy/src/test/java/org/eclipse/digitaltwin/basyx/submodelregistry/feature/hierarchy/DummySubmodelRegistryComponent.java @@ -0,0 +1,43 @@ +/******************************************************************************* + * 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.submodelregistry.feature.hierarchy; + +import org.springframework.boot.SpringApplication; +import org.springframework.boot.autoconfigure.SpringBootApplication; + +/** + * Creates and starts the {@link HierarchicalSubmodelRegistryFeature} for tests + * + * @author mateusmolina + * + */ +@SpringBootApplication(scanBasePackages = { "org.eclipse.digitaltwin.basyx.submodelregistry.feature.hierarchy", "org.eclipse.digitaltwin.basyx.submodelregistry.service.api", "org.eclipse.digitaltwin.basyx.submodelregistry.service.events", + "org.eclipse.digitaltwin.basyx.submodelregistry.service.configuration", "org.eclipse.digitaltwin.basyx.submodelregistry.service.errors", "org.eclipse.digitaltwin.basyx.common.hierarchy" }) +public class DummySubmodelRegistryComponent { + public static void main(String[] args) { + SpringApplication.run(DummySubmodelRegistryComponent.class, args); + } +} \ No newline at end of file diff --git a/basyx.submodelregistry/basyx.submodelregistry-feature-hierarchy/src/test/java/org/eclipse/digitaltwin/basyx/submodelregistry/feature/hierarchy/HierarchicalSubmodelRegistryTestSuite.java b/basyx.submodelregistry/basyx.submodelregistry-feature-hierarchy/src/test/java/org/eclipse/digitaltwin/basyx/submodelregistry/feature/hierarchy/HierarchicalSubmodelRegistryTestSuite.java new file mode 100644 index 000000000..ac24c9f46 --- /dev/null +++ b/basyx.submodelregistry/basyx.submodelregistry-feature-hierarchy/src/test/java/org/eclipse/digitaltwin/basyx/submodelregistry/feature/hierarchy/HierarchicalSubmodelRegistryTestSuite.java @@ -0,0 +1,119 @@ +/******************************************************************************* + * 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.submodelregistry.feature.hierarchy; + +import static org.junit.Assert.*; + +import java.util.Arrays; +import java.util.List; + +import org.eclipse.digitaltwin.basyx.submodelregistry.client.ApiException; +import org.eclipse.digitaltwin.basyx.submodelregistry.client.api.SubmodelRegistryApi; +import org.eclipse.digitaltwin.basyx.submodelregistry.client.model.GetSubmodelDescriptorsResult; +import org.eclipse.digitaltwin.basyx.submodelregistry.client.model.SubmodelDescriptor; +import org.junit.Before; +import org.junit.Test; +import org.springframework.http.HttpStatus; + +/** + * Test for {@link HierarchicalSubmodelRegistryFeature} + * + * @author mateusmolina + * + */ +public abstract class HierarchicalSubmodelRegistryTestSuite { + + protected abstract SubmodelRegistryApi getRootRegistryApi(); + + protected abstract SubmodelRegistryApi getDelegatedRegistryApi(); + + protected abstract DummyDescriptorFactory getDescriptorFactory(); + + @Before + public void setUp() throws ApiException { + cleanUpRegistries(); + setupDelegatedRegistry(); + setupRootRegistry(); + } + + @Test + public void getSubmodelDescriptor_InRoot() throws ApiException { + SubmodelDescriptor actualDescriptor = getRootRegistryApi().getSubmodelDescriptorById(DummyDescriptorFactory.ROOT_ONLY_SM_ID); + + SubmodelDescriptor expectedDescriptor = getDescriptorFactory().getRootOnlySmDescriptor(); + + assertEquals(expectedDescriptor, actualDescriptor); + } + + @Test + public void getSubmodelDescriptor_ThroughDelegated() throws ApiException { + SubmodelDescriptor actualDescriptor = getRootRegistryApi().getSubmodelDescriptorById(getDescriptorFactory().getDelegatedOnlySmId()); + + SubmodelDescriptor expectedDescriptor = getDescriptorFactory().getDelegatedOnlySmDescriptor(); + + assertEquals(expectedDescriptor, actualDescriptor); + } + + @Test + public void getAllSubmodelDescriptor_InRoot() throws ApiException { + GetSubmodelDescriptorsResult actualDescriptors = getRootRegistryApi().getAllSubmodelDescriptors(1, null); + + SubmodelDescriptor expectedDescriptor = getDescriptorFactory().getRootOnlySmDescriptor(); + + List expectedDescriptors = Arrays.asList(expectedDescriptor); + + assertEquals(expectedDescriptors, actualDescriptors.getResult()); + } + + @Test + public void getNonExistingSmDescriptor() { + ApiException exception = assertThrows(ApiException.class, () -> getRootRegistryApi().getSubmodelDescriptorById("nonExistingSubmodel")); + assertHttpCodeIsNotFound(exception); + + exception = assertThrows(ApiException.class, () -> getRootRegistryApi().getSubmodelDescriptorById("nonExistingSubmodel")); + assertHttpCodeIsNotFound(exception); + + } + + private void setupRootRegistry() throws ApiException { + getRootRegistryApi().postSubmodelDescriptor(getDescriptorFactory().getRootOnlySmDescriptor()); + } + + private void setupDelegatedRegistry() throws ApiException { + SubmodelDescriptor descriptor = getDescriptorFactory().getDelegatedOnlySmDescriptor(); + + getDelegatedRegistryApi().postSubmodelDescriptor(descriptor); + } + + private void cleanUpRegistries() throws ApiException { + getRootRegistryApi().deleteAllSubmodelDescriptors(); + getDelegatedRegistryApi().deleteAllSubmodelDescriptors(); + } + + private static void assertHttpCodeIsNotFound(ApiException e) { + assertEquals(HttpStatus.NOT_FOUND.value(), e.getCode()); + } +} diff --git a/basyx.submodelregistry/basyx.submodelregistry-feature-hierarchy/src/test/java/org/eclipse/digitaltwin/basyx/submodelregistry/feature/hierarchy/LocalhostHierarchicalSubmodelRegistryTest.java b/basyx.submodelregistry/basyx.submodelregistry-feature-hierarchy/src/test/java/org/eclipse/digitaltwin/basyx/submodelregistry/feature/hierarchy/LocalhostHierarchicalSubmodelRegistryTest.java new file mode 100644 index 000000000..f571c1124 --- /dev/null +++ b/basyx.submodelregistry/basyx.submodelregistry-feature-hierarchy/src/test/java/org/eclipse/digitaltwin/basyx/submodelregistry/feature/hierarchy/LocalhostHierarchicalSubmodelRegistryTest.java @@ -0,0 +1,80 @@ +/******************************************************************************* + * 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.submodelregistry.feature.hierarchy; + +import org.eclipse.digitaltwin.basyx.submodelregistry.client.api.SubmodelRegistryApi; +import org.junit.AfterClass; +import org.junit.BeforeClass; +import org.springframework.boot.SpringApplication; +import org.springframework.context.ConfigurableApplicationContext; + +/** + * LocalhostHierarchicalSubmodelRegistryTest + * + * @author mateusmolina + * + */ +public class LocalhostHierarchicalSubmodelRegistryTest extends HierarchicalSubmodelRegistryTestSuite { + + private static final String ROOT_REGISTRY_URL = "http://localhost:8081"; + private static final String DELEGATED_REGISTRY_URL = "http://localhost:8060"; + + private static final String REPO_BASE_URL = "http://localhost:8080"; + private static final String DELEGATED_SMID = "http://localhost:8060/test/sm"; + + private static final SubmodelRegistryApi rootRegistryApi = new SubmodelRegistryApi(ROOT_REGISTRY_URL); + private static final SubmodelRegistryApi delegatedRegistryApi = new SubmodelRegistryApi(DELEGATED_REGISTRY_URL); + + private static final DummyDescriptorFactory factory = new DummyDescriptorFactory(REPO_BASE_URL, DELEGATED_SMID); + + private static ConfigurableApplicationContext appContext; + + @BeforeClass + public static void setupRootRegistry() { + appContext = new SpringApplication(DummySubmodelRegistryComponent.class).run(new String[] {}); + } + + @AfterClass + public static void tearDownRootRegistry() { + appContext.close(); + } + + @Override + protected SubmodelRegistryApi getRootRegistryApi() { + return rootRegistryApi; + } + + @Override + protected SubmodelRegistryApi getDelegatedRegistryApi() { + return delegatedRegistryApi; + } + + @Override + protected DummyDescriptorFactory getDescriptorFactory() { + return factory; + } + +} diff --git a/basyx.submodelregistry/basyx.submodelregistry-feature-hierarchy/src/test/resources/application.yml b/basyx.submodelregistry/basyx.submodelregistry-feature-hierarchy/src/test/resources/application.yml new file mode 100644 index 000000000..9a6351f54 --- /dev/null +++ b/basyx.submodelregistry/basyx.submodelregistry-feature-hierarchy/src/test/resources/application.yml @@ -0,0 +1,45 @@ +--- +events: + sink: log +description: + profiles: https://admin-shell.io/aas/API/3/0/SubmodelRegistryServiceSpecification/SSP-001 +springdoc: + api-docs: + path: /api-docs +springfox: + documentation: + enabled: true + # open-api.v3.path: /api-docs +management: + endpoints: + web: + exposure: + include: "health,metrics" +logging: + level: + root: INFO +server: + shutdown: graceful + port: 8081 + error: + whitelabel: + enabled: false +spring: + application: + name: Basyx Submodel Registry + jackson: + date-format: org.eclipse.digitaltwin.basyx.submodelregistry.service.RFC3339DateFormat + serialization: + WRITE_DATES_AS_TIMESTAMPS: false + +registry: + type: inMemory + +basyx: + cors: + allowed-origins: "*" + allowed-methods: "GET,POST,PATCH,DELETE,PUT,OPTIONS,HEAD" + feature: + hierarchy: + enabled: true + prefix: diff --git a/basyx.submodelregistry/basyx.submodelregistry-service-release-kafka-mem/pom.xml b/basyx.submodelregistry/basyx.submodelregistry-service-release-kafka-mem/pom.xml index 66f1c919c..94c787593 100644 --- a/basyx.submodelregistry/basyx.submodelregistry-service-release-kafka-mem/pom.xml +++ b/basyx.submodelregistry/basyx.submodelregistry-service-release-kafka-mem/pom.xml @@ -24,6 +24,10 @@ org.eclipse.digitaltwin.basyx basyx.submodelregistry-feature-authorization + + org.eclipse.digitaltwin.basyx + basyx.submodelregistry-feature-hierarchy + org.eclipse.digitaltwin.basyx basyx.authorization diff --git a/basyx.submodelregistry/basyx.submodelregistry-service-release-kafka-mongodb/pom.xml b/basyx.submodelregistry/basyx.submodelregistry-service-release-kafka-mongodb/pom.xml index e91d80cc8..94f2dc4b2 100644 --- a/basyx.submodelregistry/basyx.submodelregistry-service-release-kafka-mongodb/pom.xml +++ b/basyx.submodelregistry/basyx.submodelregistry-service-release-kafka-mongodb/pom.xml @@ -24,6 +24,10 @@ org.eclipse.digitaltwin.basyx basyx.submodelregistry-feature-authorization + + org.eclipse.digitaltwin.basyx + basyx.submodelregistry-feature-hierarchy + org.eclipse.digitaltwin.basyx basyx.authorization diff --git a/basyx.submodelregistry/basyx.submodelregistry-service-release-log-mem/pom.xml b/basyx.submodelregistry/basyx.submodelregistry-service-release-log-mem/pom.xml index 5cf7112ad..a515a2ca0 100644 --- a/basyx.submodelregistry/basyx.submodelregistry-service-release-log-mem/pom.xml +++ b/basyx.submodelregistry/basyx.submodelregistry-service-release-log-mem/pom.xml @@ -25,6 +25,10 @@ org.eclipse.digitaltwin.basyx basyx.submodelregistry-feature-authorization + + org.eclipse.digitaltwin.basyx + basyx.submodelregistry-feature-hierarchy + org.eclipse.digitaltwin.basyx basyx.authorization diff --git a/basyx.submodelregistry/basyx.submodelregistry-service-release-log-mongodb/pom.xml b/basyx.submodelregistry/basyx.submodelregistry-service-release-log-mongodb/pom.xml index 751d571c1..6c50463fe 100644 --- a/basyx.submodelregistry/basyx.submodelregistry-service-release-log-mongodb/pom.xml +++ b/basyx.submodelregistry/basyx.submodelregistry-service-release-log-mongodb/pom.xml @@ -24,6 +24,10 @@ org.eclipse.digitaltwin.basyx basyx.submodelregistry-feature-authorization + + org.eclipse.digitaltwin.basyx + basyx.submodelregistry-feature-hierarchy + org.eclipse.digitaltwin.basyx basyx.authorization diff --git a/basyx.submodelregistry/basyx.submodelregistry-service/templates/openapi2SpringBoot.mustache b/basyx.submodelregistry/basyx.submodelregistry-service/templates/openapi2SpringBoot.mustache index 3778320ee..8805ecb78 100644 --- a/basyx.submodelregistry/basyx.submodelregistry-service/templates/openapi2SpringBoot.mustache +++ b/basyx.submodelregistry/basyx.submodelregistry-service/templates/openapi2SpringBoot.mustache @@ -15,7 +15,7 @@ import org.springframework.context.annotation.FullyQualifiedAnnotationBeanNameGe nameGenerator = FullyQualifiedAnnotationBeanNameGenerator.class ) @ComponentScan( - basePackages = {"org.eclipse.digitaltwin.basyx.authorization", "org.eclipse.digitaltwin.basyx.authorization.rbac", "org.eclipse.digitaltwin.basyx.submodelregistry.feature", "{{basePackage}}", "{{apiPackage}}" , "{{configPackage}}"}, + basePackages = {"org.eclipse.digitaltwin.basyx.authorization", "org.eclipse.digitaltwin.basyx.common.hierarchy", "org.eclipse.digitaltwin.basyx.authorization.rbac", "org.eclipse.digitaltwin.basyx.submodelregistry.feature", "{{basePackage}}", "{{apiPackage}}" , "{{configPackage}}"}, nameGenerator = FullyQualifiedAnnotationBeanNameGenerator.class ) public class OpenApiGeneratorApplication { diff --git a/basyx.submodelregistry/pom.xml b/basyx.submodelregistry/pom.xml index 8cb518ecb..f5c3028f9 100644 --- a/basyx.submodelregistry/pom.xml +++ b/basyx.submodelregistry/pom.xml @@ -51,6 +51,8 @@ basyx.submodelregistry-service-release-log-mongodb basyx.submodelregistry-service-release-kafka-mongodb basyx.submodelregistry-feature-authorization + basyx.submodelregistry-feature-hierarchy + basyx.submodelregistry-feature-hierarchy-example @@ -190,6 +192,11 @@ basyx.submodelregistry-feature-authorization ${project.version} + + org.eclipse.digitaltwin.basyx + basyx.submodelregistry-feature-hierarchy + ${project.version} + diff --git a/pom.xml b/pom.xml index 36dd3ae3b..d6f03e000 100644 --- a/pom.xml +++ b/pom.xml @@ -423,6 +423,11 @@ basyx.authorization ${revision} + + org.eclipse.digitaltwin.basyx + basyx.hierarchy + ${revision} + org.eclipse.digitaltwin.basyx basyx.authorization.rules.rbac.backend.inmemory @@ -1106,6 +1111,12 @@ ${revision} tests + + org.eclipse.digitaltwin.basyx + basyx.aasregistry-feature-hierarchy + ${revision} + tests + org.eclipse.digitaltwin.basyx basyx.aasregistry-client-native @@ -1118,6 +1129,12 @@ ${revision} tests + + org.eclipse.digitaltwin.basyx + basyx.submodelregistry-feature-hierarchy + ${revision} + tests + org.eclipse.digitaltwin.basyx basyx.submodelregistry-client-native