Skip to content

Commit dba9cf2

Browse files
authored
Add feature hierarchical registry for AAS and Submodel Registries (#549)
* wip: Add a registry hierarchy feature module to AAS Registry * wip: Revert ci compose change * wip: Rename package * wip: Initial Test Setup * wip: Implement tests for Hierarchal AasRegistry * Implement getAasDescriptor hierarchal method * Implement hierarchal getSubmodelDescriptor * Extract ApiExpectionMappers to helper class * Extract delegation strategy to own component * Clean up code and improve javadocs * Improve README * Implement prefix-based delegation strategy * Document prefix strategy in README * Implement hierarchical getAllSubmodels * Improve test understandability * Add feature to parent/components poms * Add example module * Fix feature not being found during storage creation * Refactor tests to use http for both root and delegated regs. * Refactor hierarchy-example to use mvn docker plugin to build image * Add example scenario IT to example * Add Readme to example module * Add feature to kafka releases * feat: add SubmodelRegistry Hierarchy Feature * feat: add hierarchy features to all releases * feat: add submodelRegistry Hierarchy Example * fix: aasregistry hierarchy example dockerfile failing to launch * refactor: extract DelegationStrategy to common * refactor: existing feature impl to use the common DelegationStrategy * chore: update POM names * test: fix components not finding DelegationStrategy bean * test: refactor aasregistry fixture factory to be consistent with smregistry.hierarchy * fix: it tests not working due to common.hierarchy not being found * docs: reminder of retriving all descriptors not being supported by the feature * test: remove unrelated test * docs: bump license header year; add missing
1 parent b8e0042 commit dba9cf2

File tree

53 files changed

+2342
-2
lines changed

Some content is hidden

Large Commits have some content hidden by default. Use the searchbox below for content that may be hidden.

53 files changed

+2342
-2
lines changed
Lines changed: 42 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,42 @@
1+
# AssetAdministrationShell Registry - Hierarchy - Example
2+
3+
This example showcases the working principle of the hierarchical registries feature.
4+
5+
## Scenario description
6+
7+
```mermaid
8+
sequenceDiagram
9+
actor Client
10+
participant Root as Root Registry <aas-registry-root:8080>
11+
participant Delegated as Delegated Registry <registry.delegated-aas-registry:8080>
12+
13+
Client ->> Root: Request resolution for AAS-ID "http://delegated-aas-registry:8080/test/aas"
14+
activate Root
15+
16+
Root ->> Root: Check local records
17+
alt Not found locally
18+
Root ->> Root: Determine registry based on URI prefix
19+
Root ->> Delegated: Resolve AAS-ID "http://delegated-aas-registry:8080/test/aas" at registry.delegated-aas-registry:8080
20+
activate Delegated
21+
Delegated ->> Delegated: Resolve AAS-ID
22+
Delegated ->> Root: Resolution result
23+
deactivate Delegated
24+
end
25+
26+
Root ->> Client: Return resolution result
27+
deactivate Root
28+
```
29+
30+
## Running the scenario
31+
32+
In order to run the example, please make sure that all aasregistries maven modules are correctly installed in your local Maven repository.
33+
34+
1. Generate the Docker image: `mvn clean install -Ddocker.namespace=aas-registry-test`
35+
36+
2. Run the docker compose: `docker compose up`
37+
38+
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.
39+
40+
They are visibile within the bridged Docker network as (1) aas-registry-root:8080 and (2) registry.delegated-aas-registry:8080
41+
42+
3. Run the scenario [HierarchicalAasRegistryIT](/src/test/java/org/eclipse/digitaltwin/basyx/aasregistry/feature/hierarchy/example/HierachicalAasRegistryIT.java)
Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,5 @@
1+
---
2+
basyx:
3+
cors:
4+
allowed-origins: "*"
5+
allowed-methods: "GET,POST,PATCH,DELETE,PUT,OPTIONS,HEAD"
Lines changed: 29 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,29 @@
1+
version: "3.9"
2+
services:
3+
4+
aas-registry-root:
5+
image: aas-registry-test/aas-registry-feature-hierarchy-example:2.0.0-SNAPSHOT
6+
container_name: aas-registry-root
7+
ports:
8+
- "8051:8080"
9+
environment:
10+
SERVER_SERVLET_CONTEXT_PATH: /
11+
networks:
12+
- basyx-aasregistry-feature-hierarchy-example
13+
14+
registry.delegated-aas-registry:
15+
image: eclipsebasyx/aas-registry-log-mem:2.0.0-SNAPSHOT
16+
container_name: registry.delegated-aas-registry
17+
ports:
18+
- "8052:8080"
19+
environment:
20+
SERVER_SERVLET_CONTEXT_PATH: /
21+
volumes:
22+
- ./aas-registry-delegated.yml:/workspace/config/application.yml
23+
networks:
24+
- basyx-aasregistry-feature-hierarchy-example
25+
26+
networks:
27+
basyx-aasregistry-feature-hierarchy-example:
28+
name: basyx-aasregistry-feature-hierarchy-example
29+
driver: bridge
Lines changed: 35 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,35 @@
1+
<project xmlns="http://maven.apache.org/POM/4.0.0"
2+
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
3+
xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 https://maven.apache.org/xsd/maven-4.0.0.xsd">
4+
<modelVersion>4.0.0</modelVersion>
5+
6+
<parent>
7+
<groupId>org.eclipse.digitaltwin.basyx</groupId>
8+
<artifactId>basyx.aasregistry</artifactId>
9+
<version>${revision}</version>
10+
</parent>
11+
12+
<artifactId>basyx.aasregistry-feature-hierarchy-example</artifactId>
13+
<name>BaSyx AAS Registry Feature Hierarchy Example</name>
14+
<description>BaSyx AAS Registry Feature Hierarchy Example</description>
15+
16+
<properties>
17+
<spring-cloud.version>2020.0.4</spring-cloud.version>
18+
<start-class> org.eclipse.digitaltwin.basyx.aasregistry.service.OpenApiGeneratorApplication</start-class>
19+
<docker.image.name>aas-registry-feature-hierarchy-example</docker.image.name>
20+
</properties>
21+
22+
<dependencies>
23+
<dependency>
24+
<groupId>org.eclipse.digitaltwin.basyx</groupId>
25+
<artifactId>basyx.aasregistry-service-release-log-mem</artifactId>
26+
<version>${revision}</version>
27+
</dependency>
28+
<dependency>
29+
<groupId>org.eclipse.digitaltwin.basyx</groupId>
30+
<artifactId>basyx.aasregistry-feature-hierarchy</artifactId>
31+
<classifier>tests</classifier>
32+
<scope>test</scope>
33+
</dependency>
34+
</dependencies>
35+
</project>
Lines changed: 21 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,21 @@
1+
FROM eclipse-temurin:17 as builder
2+
COPY maven/${project.build.finalName}.jar ./
3+
RUN java -Djarmode=layertools -jar ${project.build.finalName}.jar extract
4+
5+
FROM eclipse-temurin:17
6+
RUN mkdir /workspace
7+
WORKDIR /workspace
8+
COPY --from=builder dependencies/ ./
9+
COPY --from=builder snapshot-dependencies/ ./
10+
RUN true
11+
COPY --from=builder spring-boot-loader/ ./
12+
COPY --from=builder application/ ./
13+
ENV SPRING_PROFILES_ACTIVE=logEvents,inMemoryStorage,hierarchy
14+
ARG PORT=8080
15+
ENV SERVER_PORT=${PORT}
16+
ARG CONTEXT_PATH=/
17+
ENV SERVER_SERVLET_CONTEXT_PATH=${CONTEXT_PATH}
18+
EXPOSE ${SERVER_PORT}
19+
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
20+
ENTRYPOINT ["java", "-Djava.security.egd=file:/dev/.urandom", "org.springframework.boot.loader.launch.JarLauncher"]
21+
Lines changed: 9 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,9 @@
1+
---
2+
basyx:
3+
cors:
4+
allowed-origins: "*"
5+
allowed-methods: "GET,POST,PATCH,DELETE,PUT,OPTIONS,HEAD"
6+
feature:
7+
hierarchy:
8+
enabled: true
9+
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,66 @@
1+
/*******************************************************************************
2+
* Copyright (C) 2025 the Eclipse BaSyx Authors
3+
*
4+
* Permission is hereby granted, free of charge, to any person obtaining
5+
* a copy of this software and associated documentation files (the
6+
* "Software"), to deal in the Software without restriction, including
7+
* without limitation the rights to use, copy, modify, merge, publish,
8+
* distribute, sublicense, and/or sell copies of the Software, and to
9+
* permit persons to whom the Software is furnished to do so, subject to
10+
* the following conditions:
11+
*
12+
* The above copyright notice and this permission notice shall be
13+
* included in all copies or substantial portions of the Software.
14+
*
15+
* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
16+
* EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
17+
* MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
18+
* NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE
19+
* LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION
20+
* OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
21+
* WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
22+
*
23+
* SPDX-License-Identifier: MIT
24+
******************************************************************************/
25+
26+
package org.eclipse.digitaltwin.basyx.aasregistry.feature.hierarchy.example;
27+
28+
import org.eclipse.digitaltwin.basyx.aasregistry.client.api.RegistryAndDiscoveryInterfaceApi;
29+
import org.eclipse.digitaltwin.basyx.aasregistry.feature.hierarchy.DummyDescriptorFactory;
30+
import org.eclipse.digitaltwin.basyx.aasregistry.feature.hierarchy.HierarchicalAasRegistryTestSuite;
31+
32+
/**
33+
* HierachicalAasRegistryIT
34+
*
35+
* @author mateusmolina
36+
*
37+
*/
38+
public class HierachicalAasRegistryIT extends HierarchicalAasRegistryTestSuite {
39+
40+
private static final String ROOT_REGISTRY_URL = "http://localhost:8051";
41+
private static final String DELEGATED_REGISTRY_URL = "http://localhost:8052";
42+
43+
private static final String REPO_BASE_URL = "http://localhost:8080";
44+
private static final String DELEGATED_AASID = "http://delegated-aas-registry:8080/test/aas";
45+
46+
private static final DummyDescriptorFactory factory = new DummyDescriptorFactory(REPO_BASE_URL, DELEGATED_AASID);
47+
48+
private static final RegistryAndDiscoveryInterfaceApi rootRegistryApi = new RegistryAndDiscoveryInterfaceApi(ROOT_REGISTRY_URL);
49+
private static final RegistryAndDiscoveryInterfaceApi delegatedRegistryApi = new RegistryAndDiscoveryInterfaceApi(DELEGATED_REGISTRY_URL);
50+
51+
@Override
52+
protected RegistryAndDiscoveryInterfaceApi getRootRegistryApi() {
53+
return rootRegistryApi;
54+
}
55+
56+
@Override
57+
protected RegistryAndDiscoveryInterfaceApi getDelegatedRegistryApi() {
58+
return delegatedRegistryApi;
59+
}
60+
61+
@Override
62+
protected DummyDescriptorFactory getDescriptorFactory() {
63+
return factory;
64+
}
65+
66+
}
Lines changed: 33 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,33 @@
1+
# AssetAdministrationShell Registry - Hierarchy
2+
3+
The Hierarchical AasRegistry Feature enhances the availability of data across multiple registries by allowing retrieval requests to be delegated to another AasRegistry.
4+
5+
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.
6+
7+
Retrieving all the stored AasDescriptors with delegation is not currently supported.
8+
9+
## Configuration
10+
11+
### Enabling the Feature
12+
13+
To enable the Hierarchical AasRegistry Feature, add the following property to your Spring application's configuration:
14+
15+
```properties
16+
basyx.feature.hierarchy.enabled=true
17+
```
18+
19+
### Delegation Strategy
20+
21+
Currently, only one delegation strategy is implemented:
22+
23+
#### Prefix Delegation Strategy
24+
25+
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.
26+
27+
The prefix can be configured by the property `basyx.feature.hierarchy.prefix`. Please refer to the example below:
28+
29+
```properties
30+
basyx.feature.hierarchy.prefix=registry
31+
```
32+
33+
If this property is left with an empty string, no prefix is appended to the URL contained in the `aasDecriptorId`.
Lines changed: 38 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,38 @@
1+
<project xmlns="http://maven.apache.org/POM/4.0.0"
2+
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
3+
xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 https://maven.apache.org/xsd/maven-4.0.0.xsd">
4+
<modelVersion>4.0.0</modelVersion>
5+
6+
<parent>
7+
<groupId>org.eclipse.digitaltwin.basyx</groupId>
8+
<artifactId>basyx.aasregistry</artifactId>
9+
<version>${revision}</version>
10+
</parent>
11+
12+
<artifactId>basyx.aasregistry-feature-hierarchy</artifactId>
13+
<name>BaSyx AAS Registry feature-hierarchy</name>
14+
15+
<dependencies>
16+
<dependency>
17+
<groupId>org.eclipse.digitaltwin.basyx</groupId>
18+
<artifactId>basyx.hierarchy</artifactId>
19+
</dependency>
20+
<dependency>
21+
<groupId>org.eclipse.digitaltwin.basyx</groupId>
22+
<artifactId>basyx.aasregistry-service</artifactId>
23+
</dependency>
24+
<dependency>
25+
<groupId>org.eclipse.digitaltwin.basyx</groupId>
26+
<artifactId>basyx.aasregistry-service-basemodel</artifactId>
27+
</dependency>
28+
<dependency>
29+
<groupId>org.eclipse.digitaltwin.basyx</groupId>
30+
<artifactId>basyx.aasregistry-client-native</artifactId>
31+
</dependency>
32+
<dependency>
33+
<groupId>org.eclipse.digitaltwin.basyx</groupId>
34+
<artifactId>basyx.aasregistry-service-inmemory-storage</artifactId>
35+
<scope>test</scope>
36+
</dependency>
37+
</dependencies>
38+
</project>
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,97 @@
1+
/*******************************************************************************
2+
* Copyright (C) 2025 the Eclipse BaSyx Authors
3+
*
4+
* Permission is hereby granted, free of charge, to any person obtaining
5+
* a copy of this software and associated documentation files (the
6+
* "Software"), to deal in the Software without restriction, including
7+
* without limitation the rights to use, copy, modify, merge, publish,
8+
* distribute, sublicense, and/or sell copies of the Software, and to
9+
* permit persons to whom the Software is furnished to do so, subject to
10+
* the following conditions:
11+
*
12+
* The above copyright notice and this permission notice shall be
13+
* included in all copies or substantial portions of the Software.
14+
*
15+
* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
16+
* EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
17+
* MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
18+
* NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE
19+
* LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION
20+
* OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
21+
* WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
22+
*
23+
* SPDX-License-Identifier: MIT
24+
******************************************************************************/
25+
26+
package org.eclipse.digitaltwin.basyx.aasregistry.feature.hierarchy;
27+
28+
import java.util.List;
29+
30+
import org.eclipse.digitaltwin.basyx.aasregistry.client.ApiException;
31+
import org.eclipse.digitaltwin.basyx.aasregistry.client.model.GetSubmodelDescriptorsResult;
32+
import org.eclipse.digitaltwin.basyx.aasregistry.model.AssetAdministrationShellDescriptor;
33+
import org.eclipse.digitaltwin.basyx.aasregistry.model.SubmodelDescriptor;
34+
import org.eclipse.digitaltwin.basyx.aasregistry.service.errors.AasDescriptorNotFoundException;
35+
import org.eclipse.digitaltwin.basyx.aasregistry.service.errors.SubmodelNotFoundException;
36+
import org.eclipse.digitaltwin.basyx.core.pagination.CursorResult;
37+
import org.springframework.http.HttpStatus;
38+
import org.springframework.http.HttpStatusCode;
39+
40+
import com.fasterxml.jackson.core.type.TypeReference;
41+
import com.fasterxml.jackson.databind.ObjectMapper;
42+
43+
/**
44+
* Handles internal model mapping for {@link HierarchicalAasRegistryStorage}
45+
*
46+
* @author mateusmolina
47+
*
48+
*/
49+
final class AasRegistryModelMapper {
50+
51+
private static final ObjectMapper objectMapper = new ObjectMapper();
52+
53+
private AasRegistryModelMapper() {
54+
}
55+
56+
static org.eclipse.digitaltwin.basyx.aasregistry.client.model.AssetAdministrationShellDescriptor mapEqModel(AssetAdministrationShellDescriptor aasRegistryDescriptor) {
57+
return objectMapper.convertValue(aasRegistryDescriptor, org.eclipse.digitaltwin.basyx.aasregistry.client.model.AssetAdministrationShellDescriptor.class);
58+
}
59+
60+
static AssetAdministrationShellDescriptor mapEqModel(org.eclipse.digitaltwin.basyx.aasregistry.client.model.AssetAdministrationShellDescriptor aasRegistryDescriptor) {
61+
return objectMapper.convertValue(aasRegistryDescriptor, AssetAdministrationShellDescriptor.class);
62+
}
63+
64+
static org.eclipse.digitaltwin.basyx.aasregistry.client.model.SubmodelDescriptor mapEqModel(SubmodelDescriptor smRegistryDescriptor) {
65+
return objectMapper.convertValue(smRegistryDescriptor, org.eclipse.digitaltwin.basyx.aasregistry.client.model.SubmodelDescriptor.class);
66+
}
67+
68+
static SubmodelDescriptor mapEqModel(org.eclipse.digitaltwin.basyx.aasregistry.client.model.SubmodelDescriptor smRegistryDescriptor) {
69+
return objectMapper.convertValue(smRegistryDescriptor, SubmodelDescriptor.class);
70+
}
71+
72+
static CursorResult<List<SubmodelDescriptor>> mapEqModel(GetSubmodelDescriptorsResult descriptorResult) {
73+
List<SubmodelDescriptor> submodelDescs = objectMapper.convertValue(descriptorResult.getResult(), new TypeReference<List<SubmodelDescriptor>>() {
74+
});
75+
return new CursorResult<>(descriptorResult.getPagingMetadata().getCursor(), submodelDescs);
76+
}
77+
78+
static RuntimeException mapApiException(ApiException e, String aasDescriptorId) {
79+
if (HttpStatusCode.valueOf(e.getCode()).equals(HttpStatus.NOT_FOUND))
80+
return new AasDescriptorNotFoundException(aasDescriptorId);
81+
82+
return new RuntimeException(e);
83+
}
84+
85+
static RuntimeException mapApiException(ApiException e, String aasDescriptorId, String smId) {
86+
if (HttpStatusCode.valueOf(e.getCode()).equals(HttpStatus.NOT_FOUND) && checkIfSubmodelNotFound(e, aasDescriptorId, smId))
87+
return new SubmodelNotFoundException(aasDescriptorId, smId);
88+
89+
return mapApiException(e, aasDescriptorId);
90+
}
91+
92+
private static boolean checkIfSubmodelNotFound(ApiException e, String aasDescriptorId, String smId) {
93+
SubmodelNotFoundException expectedException = new SubmodelNotFoundException(aasDescriptorId, smId);
94+
return e.getMessage().contains(expectedException.getReason());
95+
}
96+
97+
}

0 commit comments

Comments
 (0)