diff --git a/.github/workflows/basyx_test.yml b/.github/workflows/basyx_test.yml
index 15358f744..e4929c3c0 100644
--- a/.github/workflows/basyx_test.yml
+++ b/.github/workflows/basyx_test.yml
@@ -736,6 +736,39 @@ jobs:
exit 1;
fi
+ test-basyx-aasdigitaltwinregistry:
+ runs-on: ubuntu-latest
+ name: AAS Digital Twin Registry Service Test
+ steps:
+ - uses: actions/checkout@v4
+
+ - name: Set up JDK 17
+ uses: actions/setup-java@v4
+ with:
+ java-version: '17'
+ distribution: 'adopt'
+ cache: maven
+
+ - name: Build BaSyx
+ run: mvn clean install ${MVN_ARGS_BUILD_BASYX}
+
+ - name: Test AAS Digital Twin Registry Service
+ run: mvn test -f "basyx.aasdigitaltwinregistry/pom.xml"
+
+ - name: Fail if no Surefire/Failsafe reports found
+ run: |
+ if ! find . -type f \( -path "*/target/surefire-reports/*.xml" -o -path "*/target/failsafe-reports/*.xml" \) | grep -q .; then
+ echo "No Surefire or Failsafe test reports found. Failing CI.";
+ exit 1;
+ fi
+
+ - name: Fail if no Surefire/Failsafe reports found
+ run: |
+ if ! find . -type f \( -path "*/target/surefire-reports/*.xml" -o -path "*/target/failsafe-reports/*.xml" \) | grep -q .; then
+ echo "No Surefire or Failsafe test reports found. Failing CI.";
+ exit 1;
+ fi
+
- name: Stop environment
if: always()
run: docker compose --project-directory ./ci down
diff --git a/.github/workflows/docker-milestone-release.yml b/.github/workflows/docker-milestone-release.yml
index 39f935be0..8cec07cb6 100644
--- a/.github/workflows/docker-milestone-release.yml
+++ b/.github/workflows/docker-milestone-release.yml
@@ -43,6 +43,8 @@ jobs:
path: basyx.submodelregistry/basyx.submodelregistry-service-release-log-mem/src/main/docker
- name: submodel-registry-log-mongodb
path: basyx.submodelregistry/basyx.submodelregistry-service-release-log-mongodb/src/main/docker
+ - name: aas-digitaltwinregistry
+ path: basyx.aasdigitaltwinregistry/basyx.digitaltwinregistry.component
steps:
- name: Checkout Code
diff --git a/.github/workflows/docker-snapshot-release.yml b/.github/workflows/docker-snapshot-release.yml
index 08e2c71b9..f0a317347 100644
--- a/.github/workflows/docker-snapshot-release.yml
+++ b/.github/workflows/docker-snapshot-release.yml
@@ -59,6 +59,8 @@ jobs:
path: basyx.submodelregistry/basyx.submodelregistry-service-release-log-mem/src/main/docker
- name: submodel-registry-log-mongodb
path: basyx.submodelregistry/basyx.submodelregistry-service-release-log-mongodb/src/main/docker
+ - name: aas-digitaltwinregistry
+ path: basyx.aasdigitaltwinregistry/basyx.digitaltwinregistry.component
steps:
- name: Checkout Code
diff --git a/basyx.aasdigitaltwinregistry/Readme.md b/basyx.aasdigitaltwinregistry/Readme.md
new file mode 100644
index 000000000..5ed4a0db2
--- /dev/null
+++ b/basyx.aasdigitaltwinregistry/Readme.md
@@ -0,0 +1,151 @@
+
+
+# BaSyx Digital Twin Registry
+
+## Overview
+The **Digital Twin Registry** serves as a combined module that merges the capabilities of `AASRegistry` and `AASDiscovery`.
+When a client calls the `/shell-description` endpoint, the module dynamically constructs both an `AssetAdministrationShellDescriptor` and an `aasDiscoveryDocumentEntity`.
+
+This dual-output ensures that the asset shell becomes immediately discoverable and accessible, blending registry and discovery functionalities in a seamless operation.
+
+---
+
+## How It Works
+
+- **Endpoint Integration**
+
+ A single REST endpoint (`/shell-description`) triggers the generation of:
+ - An **AAS Descriptor**, representing the asset's metadata and management interface.
+ - A **Discovery Document**, enabling other components to locate or resolve the AAS.
+
+- **Unified Workflow**
+ By combining `AASRegistry` and `AASDiscovery`, the module streamlines the typical sequential two-step — *discover then retrieve* — into a single integrated operation.
+
+---
+
+## Module Structure in the BaSyx SDK
+
+- **New Module Introduction**
+ Within the main BaSyx SDK, a new module — `digitaltwinregistry` — has been introduced.
+ It follows the **decorator pattern**, meaning it wraps around existing functionality to extend behavior without modifying original code.
+
+- **Delegate-Based Design**
+ At its core, the module implements or creates a **delegate** for the `ShellDescriptorsApiDelegate` interface.
+ This delegate intercepts API calls (particularly related to shell descriptions) and injects the registry-and-discovery logic — making the module effectively pluggable and maintainable.
+
+---
+
+## Summary
+
+In essence, the **Digital Twin Registry module**:
+
+- Combines **registry** and **discovery** into a unified action via `/shell-description`.
+- Is implemented as a **decorator delegate** (`ShellDescriptorsApiDelegate`), making it both modular and maintainable.
+- Seamlessly integrates with existing BaSyx storage options and aligns with broader architectural goals, such as centralized registries, tagging, and scalable discovery.
+
+## Environment
+This document describes the environment variables used to configure the BaSyx Digital Twin Registry application. The application supports multiple profiles with different storage backends.
+
+---
+
+## Configuration Files
+The application uses three YAML configuration files:
+
+- `application.yml` - Base configuration
+- `application-InMemory.yml` - In-memory storage profile
+- `application-MongoDB.yml` - MongoDB storage profile
+
+---
+
+## Environment Variables
+
+### Base Configuration (`application.yml`)
+
+| Environment Variable | Default Value | Description |
+|-----------------------|---------------|-------------|
+| `SPRING_PROFILE` | MongoDB | Active Spring profile (`InMemory` or `MongoDB`) |
+| `LOGGING_LEVEL` | INFO | Logging level for root and BaSyx components |
+
+---
+
+### Server Configuration
+
+| Property | Default Value | Description |
+|----------------|---------------|-------------|
+| `server.port` | 8081 | HTTP server port |
+
+---
+
+### CORS Configuration
+
+| Property | Value | Description |
+|--------------------------------|-----------------------------------------------------|-------------|
+| `basyx.cors.allowed-methods` | GET,POST,PATCH,DELETE,PUT,OPTIONS,HEAD | Allowed HTTP methods |
+| `basyx.cors.allowed-origins` | * | Allowed origins (CORS) |
+
+---
+
+### Management Endpoints
+
+| Property | Value | Description |
+|------------------------------------------|------------------------------|-------------|
+| `management.endpoints.web.exposure.include` | health,metrics,mappings | Exposed actuator endpoints |
+
+---
+
+### SpringDoc/Swagger Configuration
+
+| Property | Value | Description |
+|-----------------------------------|--------------------|-------------|
+| `springdoc.api-docs.enabled` | true | Enable API documentation |
+| `springdoc.swagger-ui.enabled` | true | Enable Swagger UI |
+| `springdoc.swagger-ui.path` | /swagger-ui.html | Swagger UI path |
+| `springdoc.swagger-ui.csrf.enabled` | false | Disable CSRF protection for Swagger |
+
+---
+
+## InMemory Profile Configuration
+
+**Profile Name:** `InMemory`
+
+### Environment Variables
+_No additional environment variables required for InMemory profile._
+
+### Configuration Properties
+
+| Property | Value | Description |
+|---------------------|------------|-------------|
+| `basyx.backend` | InMemory | Use in-memory storage backend |
+| `registry.type` | InMemory | Registry type |
+| `registry.discovery.enabled` | true | Enable discovery service |
+
+### Auto-configuration Exclusions
+The InMemory profile excludes MongoDB auto-configuration:
+
+- `org.springframework.boot.autoconfigure.mongo.MongoAutoConfiguration`
+- `org.springframework.boot.autoconfigure.data.mongo.MongoDataAutoConfiguration`
+
+---
+
+## MongoDB Profile Configuration
+
+**Profile Name:** `MongoDB`
+
+### Environment Variables
+
+| Environment Variable | Default Value | Description |
+|---------------------------|-----------------------------------------|-------------|
+| `AUTHENTICATION_DATABASE` | aasregistry | MongoDB authentication database name |
+| `DATABASE_HOST` | localhost | MongoDB host address |
+| `DATABASE_PORT` | localhost | MongoDB port (**Note:** should be a numeric port) |
+| `DATABASE_USERNAME` | smartsystemhub | MongoDB username |
+| `DATABASE_PASSWORD` | smartsystemshubdatabaseforfactoryX | MongoDB password |
+
+### Configuration Properties
+
+| Property | Value | Description |
+|---------------------|---------|-------------|
+| `basyx.backend` | MongoDB | Use MongoDB storage backend |
+| `registry.type` | MongoDB | Registry type |
+| `registry.discovery.enabled` | true | Enable discovery service |
+| `basyx.aasdiscoveryservice.mongodb.collectionName` | aasregistry | MongoDB collection name |
diff --git a/basyx.aasdigitaltwinregistry/basyx.digitaltwinregistry.component/Dockerfile b/basyx.aasdigitaltwinregistry/basyx.digitaltwinregistry.component/Dockerfile
new file mode 100644
index 000000000..28a25d9fd
--- /dev/null
+++ b/basyx.aasdigitaltwinregistry/basyx.digitaltwinregistry.component/Dockerfile
@@ -0,0 +1,22 @@
+FROM eclipse-temurin:17
+USER nobody
+WORKDIR /application
+ARG JAVA_OPTS
+ENV JAVA_OPTS=$JAVA_OPTS
+ARG JAR_FILE=target/*.jar
+COPY ${JAR_FILE} basyxExecutable.jar
+
+COPY src/main/resources/application.yml application.yml
+COPY src/main/resources/application-MongoDB.yml application-MongoDB.yml
+COPY src/main/resources/application-InMemory.yml application-InMemory.yml
+
+ARG PORT=8081
+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 exec java $JAVA_OPTS -Djava.security.egd=file:/dev/./urandom -jar basyxExecutable.jar
\ No newline at end of file
diff --git a/basyx.aasdigitaltwinregistry/basyx.digitaltwinregistry.component/pom.xml b/basyx.aasdigitaltwinregistry/basyx.digitaltwinregistry.component/pom.xml
new file mode 100644
index 000000000..6e0ea9ec7
--- /dev/null
+++ b/basyx.aasdigitaltwinregistry/basyx.digitaltwinregistry.component/pom.xml
@@ -0,0 +1,219 @@
+
+
+ 4.0.0
+
+
+ org.eclipse.digitaltwin.basyx
+ basyx.digitaltwinregistry
+ ${revision}
+
+
+ basyx.digitaltwinregistry.component
+ BaSyx Digital Twin Registry Component
+ BaSyx Digital Twin Registry Component
+
+
+ 17
+ ${java.version}
+ ${java.version}
+
+
+
+
+ org.springframework.boot
+ spring-boot-starter
+
+
+ org.springframework.boot
+ spring-boot-starter-web
+
+
+ org.springframework.boot
+ spring-boot-starter-oauth2-resource-server
+
+
+ org.springframework.boot
+ spring-boot-starter-actuator
+
+
+
+ org.eclipse.digitaltwin.basyx
+ basyx.aasregistry-client-native
+ ${revision}
+
+
+ org.eclipse.digitaltwin.basyx
+ basyx.aasregistry-feature-authorization
+ ${revision}
+
+
+ org.eclipse.digitaltwin.basyx
+ basyx.aasregistry-feature-hierarchy
+ ${revision}
+
+
+ org.eclipse.digitaltwin.basyx
+ basyx.aasregistry-feature-hierarchy-example
+ ${revision}
+
+
+ org.eclipse.digitaltwin.basyx
+ basyx.aasregistry-paths
+ ${revision}
+
+
+ org.eclipse.digitaltwin.basyx
+ basyx.aasregistry-plugins
+ ${revision}
+
+
+ org.eclipse.digitaltwin.basyx
+ basyx.aasregistry-service
+ ${revision}
+
+
+ org.eclipse.digitaltwin.basyx
+ basyx.aasregistry-service-basemodel
+ ${revision}
+
+
+ org.eclipse.digitaltwin.basyx
+ basyx.aasregistry-service-basetests
+ ${revision}
+
+
+ org.eclipse.digitaltwin.basyx
+ basyx.aasregistry-service-inmemory-storage
+ ${revision}
+
+
+ org.eclipse.digitaltwin.basyx
+ basyx.aasregistry-service-kafka-events
+ ${revision}
+
+
+ org.eclipse.digitaltwin.basyx
+ basyx.aasregistry-service-mongodb-storage
+ ${revision}
+
+
+
+ org.eclipse.digitaltwin.basyx
+ basyx.aasregistry-service-release-kafka-mem
+ ${revision}
+
+
+ org.eclipse.digitaltwin.basyx
+ basyx.aasregistry-service-release-kafka-mongodb
+ ${revision}
+
+
+ org.eclipse.digitaltwin.basyx
+ basyx.aasregistry-service-release-log-mem
+ ${revision}
+
+
+ org.eclipse.digitaltwin.basyx
+ basyx.aasregistry-service-release-log-mongodb
+ ${revision}
+
+
+ org.eclipse.digitaltwin.basyx
+ basyx.aasdiscoveryservice.component
+ ${revision}
+
+
+ org.eclipse.digitaltwin.basyx
+ basyx.aasdiscoveryservice-http
+ ${revision}
+
+
+ org.eclipse.digitaltwin.basyx
+ basyx.aasdiscoveryservice-core
+ ${revision}
+
+
+ org.eclipse.digitaltwin.basyx
+ basyx.aasdiscoveryservice-backend-mongodb
+ ${revision}
+
+
+ org.eclipse.digitaltwin.basyx
+ basyx.aasdiscoveryservice-backend
+ ${revision}
+
+
+ org.eclipse.digitaltwin.basyx
+ basyx.aasdiscoveryservice-feature-authorization
+ ${revision}
+
+
+
+ org.eclipse.digitaltwin.aas4j
+ aas4j-dataformat-xml
+
+
+ org.eclipse.digitaltwin.aas4j
+ aas4j-dataformat-aasx
+
+
+ org.eclipse.digitaltwin.aas4j
+ aas4j-model
+
+
+
+ org.xmlunit
+ xmlunit-core
+
+
+ org.xmlunit
+ xmlunit-matchers
+
+
+
+ com.fasterxml.woodstox
+ woodstox-core
+ 7.1.1
+
+
+ org.codehaus.woodstox
+ stax2-api
+ 4.2.2
+
+
+
+ org.springframework.boot
+ spring-boot-starter-test
+ test
+
+
+ org.projectlombok
+ lombok
+
+
+ org.eclipse.digitaltwin.basyx
+ basyx.aasdiscoveryservice-client
+
+
+
+
+
+
+ org.springframework.boot
+ spring-boot-maven-plugin
+
+ org.eclipse.digitaltwin.basyx.aasdigitaltwinregistry.component.DigitalTwinRegistry
+
+
+
+
+ repackage
+
+
+
+
+
+
+
\ No newline at end of file
diff --git a/basyx.aasdigitaltwinregistry/basyx.digitaltwinregistry.component/src/main/java/org/eclipse/digitaltwin/basyx/aasdigitaltwinregistry/component/DigitalTwinRegistry.java b/basyx.aasdigitaltwinregistry/basyx.digitaltwinregistry.component/src/main/java/org/eclipse/digitaltwin/basyx/aasdigitaltwinregistry/component/DigitalTwinRegistry.java
new file mode 100644
index 000000000..be8860699
--- /dev/null
+++ b/basyx.aasdigitaltwinregistry/basyx.digitaltwinregistry.component/src/main/java/org/eclipse/digitaltwin/basyx/aasdigitaltwinregistry/component/DigitalTwinRegistry.java
@@ -0,0 +1,106 @@
+/*******************************************************************************
+ * Copyright (C) 2024 the Eclipse BaSyx Authors
+ *
+ * Permission is hereby granted, free of charge, to any person obtaining
+ * a copy of this software and associated documentation files (the
+ * "Software"), to deal in the Software without restriction, including
+ * without limitation the rights to use, copy, modify, merge, publish,
+ * distribute, sublicense, and/or sell copies of the Software, and to
+ * permit persons to whom the Software is furnished to do so, subject to
+ * the following conditions:
+ *
+ * The above copyright notice and this permission notice shall be
+ * included in all copies or substantial portions of the Software.
+ *
+ * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
+ * EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
+ * MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
+ * NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE
+ * LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION
+ * OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
+ * WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
+ *
+ * SPDX-License-Identifier: MIT
+ ******************************************************************************/
+
+package org.eclipse.digitaltwin.basyx.aasdigitaltwinregistry.component;
+
+import org.eclipse.digitaltwin.basyx.aasdiscoveryservice.component.AasDiscoveryServiceComponent;
+import org.eclipse.digitaltwin.basyx.aasregistry.service.api.*;
+
+import org.eclipse.digitaltwin.basyx.aasregistry.service.configuration.HomeController;
+import org.eclipse.digitaltwin.basyx.aasregistry.service.events.RegistryEventLogSink;
+import org.springframework.boot.SpringApplication;
+import org.springframework.boot.autoconfigure.SpringBootApplication;
+import org.springframework.context.annotation.ComponentScan;
+import org.springframework.context.annotation.FilterType;
+
+@SpringBootApplication
+
+@ComponentScan(
+ basePackages = {
+ "org.eclipse.digitaltwin.basyx"
+ },
+ excludeFilters = {
+
+ @ComponentScan.Filter(
+ type = FilterType.ASSIGNABLE_TYPE,
+ value = BasyxSearchApiDelegate.class
+ ),
+ @ComponentScan.Filter(
+ type = FilterType.ASSIGNABLE_TYPE,
+ value = BasyxDescriptionApiDelegate.class
+ ),
+ @ComponentScan.Filter(
+ type = FilterType.ASSIGNABLE_TYPE,
+ value = BasyxRegistryApiDelegate.class
+ ),
+ @ComponentScan.Filter(
+ type = FilterType.ASSIGNABLE_TYPE,
+ value = org.eclipse.digitaltwin.basyx.aasregistry.service.api.DescriptionApiController.class
+ ),
+ @ComponentScan.Filter(
+ type = FilterType.ASSIGNABLE_TYPE,
+ value = RegistryEventLogSink.class
+ ),
+ @ComponentScan.Filter(
+ type = FilterType.ASSIGNABLE_TYPE,
+ value = org.eclipse.digitaltwin.basyx.aasregistry.service.api.DescriptionApi.class
+ ),
+ @ComponentScan.Filter(
+ type = FilterType.ASSIGNABLE_TYPE,
+ value = org.eclipse.digitaltwin.basyx.http.description.DescriptionController.class
+ ),
+ @ComponentScan.Filter(
+ type = FilterType.ASSIGNABLE_TYPE,
+ value = DescriptionApiDelegate.class
+ ),
+ @ComponentScan.Filter(
+ type = FilterType.ASSIGNABLE_TYPE,
+ value = SearchApiController.class
+ ),
+ @ComponentScan.Filter(
+ type = FilterType.ASSIGNABLE_TYPE,
+ value = ShellDescriptorsApiController.class
+ ),
+ @ComponentScan.Filter(
+ type = FilterType.ASSIGNABLE_TYPE,
+ value = HomeController.class
+ ),
+ @ComponentScan.Filter(
+ type = FilterType.ASSIGNABLE_TYPE,
+ value = AasDiscoveryServiceComponent.class
+ ),
+ @ComponentScan.Filter(
+ type = FilterType.ASSIGNABLE_TYPE,
+ value = org.eclipse.digitaltwin.basyx.aasregistry.service.configuration.SpringDocConfiguration.class),
+ @ComponentScan.Filter(
+ type = FilterType.ASSIGNABLE_TYPE,
+ value = org.eclipse.digitaltwin.basyx.aasdiscoveryservice.http.documentation.AasDiscoveryServiceApiDocumentationConfiguration.class)
+ }
+)
+public class DigitalTwinRegistry {
+ public static void main(String[] args) {
+ SpringApplication.run(DigitalTwinRegistry.class, args);
+ }
+}
\ No newline at end of file
diff --git a/basyx.aasdigitaltwinregistry/basyx.digitaltwinregistry.component/src/main/java/org/eclipse/digitaltwin/basyx/aasdigitaltwinregistry/component/configuration/BeanExclusionConfig.java b/basyx.aasdigitaltwinregistry/basyx.digitaltwinregistry.component/src/main/java/org/eclipse/digitaltwin/basyx/aasdigitaltwinregistry/component/configuration/BeanExclusionConfig.java
new file mode 100644
index 000000000..084494438
--- /dev/null
+++ b/basyx.aasdigitaltwinregistry/basyx.digitaltwinregistry.component/src/main/java/org/eclipse/digitaltwin/basyx/aasdigitaltwinregistry/component/configuration/BeanExclusionConfig.java
@@ -0,0 +1,58 @@
+/*******************************************************************************
+ * 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.aasdigitaltwinregistry.component.configuration;
+
+import lombok.AllArgsConstructor;
+import org.springframework.beans.factory.config.BeanFactoryPostProcessor;
+import org.springframework.beans.factory.support.DefaultListableBeanFactory;
+import org.springframework.context.annotation.Bean;
+import org.springframework.context.annotation.Configuration;
+
+@Configuration
+@AllArgsConstructor
+public class BeanExclusionConfig {
+
+ @Bean
+ public static BeanFactoryPostProcessor removeDuplicateControllers() {
+ return beanFactory -> {
+ DefaultListableBeanFactory factory = (DefaultListableBeanFactory) beanFactory;
+
+ String[] duplicateBeans = {
+ "descriptionApiController",
+ "searchApiController",
+ "shellDescriptorsApiController",
+ "homeController",
+ "registryEventLogSink"
+ };
+
+ for (String beanName : duplicateBeans) {
+ if (factory.containsBeanDefinition(beanName)) {
+ factory.removeBeanDefinition(beanName);
+ }
+ }
+ };
+ }
+}
\ No newline at end of file
diff --git a/basyx.aasdigitaltwinregistry/basyx.digitaltwinregistry.component/src/main/java/org/eclipse/digitaltwin/basyx/aasdigitaltwinregistry/component/configuration/MongoStorageConfig.java b/basyx.aasdigitaltwinregistry/basyx.digitaltwinregistry.component/src/main/java/org/eclipse/digitaltwin/basyx/aasdigitaltwinregistry/component/configuration/MongoStorageConfig.java
new file mode 100644
index 000000000..5e4335231
--- /dev/null
+++ b/basyx.aasdigitaltwinregistry/basyx.digitaltwinregistry.component/src/main/java/org/eclipse/digitaltwin/basyx/aasdigitaltwinregistry/component/configuration/MongoStorageConfig.java
@@ -0,0 +1,42 @@
+/*******************************************************************************
+ * Copyright (C) 2024 the Eclipse BaSyx Authors
+ *
+ * Permission is hereby granted, free of charge, to any person obtaining
+ * a copy of this software and associated documentation files (the
+ * "Software"), to deal in the Software without restriction, including
+ * without limitation the rights to use, copy, modify, merge, publish,
+ * distribute, sublicense, and/or sell copies of the Software, and to
+ * permit persons to whom the Software is furnished to do so, subject to
+ * the following conditions:
+ *
+ * The above copyright notice and this permission notice shall be
+ * included in all copies or substantial portions of the Software.
+ *
+ * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
+ * EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
+ * MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
+ * NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE
+ * LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION
+ * OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
+ * WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
+ *
+ * SPDX-License-Identifier: MIT
+ ******************************************************************************/
+
+package org.eclipse.digitaltwin.basyx.aasdigitaltwinregistry.component.configuration;
+
+import lombok.RequiredArgsConstructor;
+import org.eclipse.digitaltwin.basyx.aasdiscoveryservice.backend.AasDiscoveryDocumentBackend;
+import org.eclipse.digitaltwin.basyx.aasdiscoveryservice.backend.mongodb.backend.MongoDBCrudAasDiscovery;
+import org.springframework.context.annotation.Bean;
+import org.springframework.context.annotation.Configuration;
+
+@Configuration
+@RequiredArgsConstructor
+public class MongoStorageConfig {
+
+ @Bean
+ public MongoDBCrudAasDiscovery mongoDBCrudAasDiscoveryBean(AasDiscoveryDocumentBackend aasDiscoveryDocumentBackend) {
+ return new MongoDBCrudAasDiscovery(aasDiscoveryDocumentBackend, "Discovery");
+ }
+}
diff --git a/basyx.aasdigitaltwinregistry/basyx.digitaltwinregistry.component/src/main/java/org/eclipse/digitaltwin/basyx/aasdigitaltwinregistry/component/controllerAdvice/GlobalExceptionHandler.java b/basyx.aasdigitaltwinregistry/basyx.digitaltwinregistry.component/src/main/java/org/eclipse/digitaltwin/basyx/aasdigitaltwinregistry/component/controllerAdvice/GlobalExceptionHandler.java
new file mode 100644
index 000000000..ce5a5c203
--- /dev/null
+++ b/basyx.aasdigitaltwinregistry/basyx.digitaltwinregistry.component/src/main/java/org/eclipse/digitaltwin/basyx/aasdigitaltwinregistry/component/controllerAdvice/GlobalExceptionHandler.java
@@ -0,0 +1,173 @@
+/*******************************************************************************
+ * Copyright (C) 2024 the Eclipse BaSyx Authors
+ *
+ * Permission is hereby granted, free of charge, to any person obtaining
+ * a copy of this software and associated documentation files (the
+ * "Software"), to deal in the Software without restriction, including
+ * without limitation the rights to use, copy, modify, merge, publish,
+ * distribute, sublicense, and/or sell copies of the Software, and to
+ * permit persons to whom the Software is furnished to do so, subject to
+ * the following conditions:
+ *
+ * The above copyright notice and this permission notice shall be
+ * included in all copies or substantial portions of the Software.
+ *
+ * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
+ * EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
+ * MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
+ * NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE
+ * LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION
+ * OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
+ * WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
+ *
+ * SPDX-License-Identifier: MIT
+ ******************************************************************************/
+
+package org.eclipse.digitaltwin.basyx.aasdigitaltwinregistry.component.controllerAdvice;
+
+import java.time.OffsetDateTime;
+
+import lombok.extern.slf4j.Slf4j;
+import org.eclipse.digitaltwin.basyx.aasregistry.model.Message;
+import org.eclipse.digitaltwin.basyx.aasregistry.model.Result;
+import org.eclipse.digitaltwin.basyx.aasregistry.model.Message.MessageTypeEnum;
+import org.eclipse.digitaltwin.basyx.core.exceptions.AssetLinkDoesNotExistException;
+import org.eclipse.digitaltwin.basyx.core.exceptions.CollidingAssetLinkException;
+import org.eclipse.digitaltwin.basyx.core.exceptions.CollidingIdentifierException;
+import org.eclipse.digitaltwin.basyx.core.exceptions.ElementDoesNotExistException;
+import org.eclipse.digitaltwin.basyx.core.exceptions.ElementNotAFileException;
+import org.eclipse.digitaltwin.basyx.core.exceptions.FeatureNotSupportedException;
+import org.eclipse.digitaltwin.basyx.core.exceptions.FileDoesNotExistException;
+import org.eclipse.digitaltwin.basyx.core.exceptions.IdentificationMismatchException;
+import org.eclipse.digitaltwin.basyx.core.exceptions.InsufficientPermissionException;
+import org.eclipse.digitaltwin.basyx.core.exceptions.MissingIdentifierException;
+import org.eclipse.digitaltwin.basyx.core.exceptions.NotInvokableException;
+import org.eclipse.digitaltwin.basyx.core.exceptions.NullSubjectException;
+import org.eclipse.digitaltwin.basyx.core.exceptions.OperationDelegationException;
+import org.springframework.http.HttpStatus;
+import org.springframework.http.MediaType;
+import org.springframework.http.ResponseEntity;
+import org.springframework.validation.ObjectError;
+import org.springframework.web.bind.MethodArgumentNotValidException;
+import org.springframework.web.bind.annotation.ControllerAdvice;
+import org.springframework.web.bind.annotation.ExceptionHandler;
+import org.springframework.web.context.request.WebRequest;
+import org.springframework.web.server.ResponseStatusException;
+
+@ControllerAdvice
+@Slf4j
+public class GlobalExceptionHandler {
+
+ @ExceptionHandler(MethodArgumentNotValidException.class)
+ public ResponseEntity handleValidationException(MethodArgumentNotValidException ex) {
+ Result result = new Result();
+ OffsetDateTime timestamp = OffsetDateTime.now();
+ String reason = HttpStatus.BAD_REQUEST.getReasonPhrase();
+ for (ObjectError error : ex.getAllErrors()) {
+ result.addMessagesItem(new Message().code(reason).messageType(MessageTypeEnum.EXCEPTION).text(error.toString()).timestamp(timestamp));
+ }
+ return new ResponseEntity<>(result, HttpStatus.BAD_REQUEST);
+ }
+
+ @ExceptionHandler(ResponseStatusException.class)
+ public ResponseEntity handleExceptions(ResponseStatusException ex) {
+ return newResultEntity(ex, HttpStatus.valueOf(ex.getStatusCode().value()));
+ }
+
+ @ExceptionHandler(Exception.class)
+ public ResponseEntity handleExceptions(Exception ex) {
+ return newResultEntity(ex, HttpStatus.INTERNAL_SERVER_ERROR);
+ }
+
+ @ExceptionHandler(ElementDoesNotExistException.class)
+ public ResponseEntity handleElementNotFoundException(ElementDoesNotExistException exception, WebRequest request) {
+ return new ResponseEntity<>(HttpStatus.NOT_FOUND);
+ }
+
+ @ExceptionHandler(AssetLinkDoesNotExistException.class)
+ public ResponseEntity handleElementNotFoundException(AssetLinkDoesNotExistException exception, WebRequest request) {
+ return new ResponseEntity<>(HttpStatus.NOT_FOUND);
+ }
+
+ @ExceptionHandler(FileDoesNotExistException.class)
+ public ResponseEntity handleElementNotFoundException(FileDoesNotExistException exception, WebRequest request) {
+ return new ResponseEntity<>(HttpStatus.NOT_FOUND);
+ }
+
+ @ExceptionHandler(CollidingIdentifierException.class)
+ public ResponseEntity handleCollidingIdentifierException(CollidingIdentifierException exception, WebRequest request) {
+ return new ResponseEntity<>(HttpStatus.CONFLICT);
+ }
+
+ @ExceptionHandler(MissingIdentifierException.class)
+ public ResponseEntity handleMissingIdentifierException(MissingIdentifierException exception, WebRequest request) {
+ return new ResponseEntity<>(HttpStatus.BAD_REQUEST);
+ }
+
+ @ExceptionHandler(CollidingAssetLinkException.class)
+ public ResponseEntity handleCollidingIdentifierException(CollidingAssetLinkException exception, WebRequest request) {
+ return new ResponseEntity<>(HttpStatus.CONFLICT);
+ }
+
+ @ExceptionHandler(IllegalArgumentException.class)
+ public ResponseEntity handleIllegalArgumentException(IllegalArgumentException exception) {
+ return new ResponseEntity<>(HttpStatus.BAD_REQUEST);
+ }
+
+ @ExceptionHandler(IdentificationMismatchException.class)
+ public ResponseEntity handleIdMismatchException(IdentificationMismatchException exception) {
+ return new ResponseEntity<>(HttpStatus.BAD_REQUEST);
+ }
+
+ @ExceptionHandler(FeatureNotSupportedException.class)
+ public ResponseEntity handleFeatureNotSupportedException(FeatureNotSupportedException exception) {
+ return new ResponseEntity<>(HttpStatus.NOT_IMPLEMENTED);
+ }
+
+ @ExceptionHandler(NotInvokableException.class)
+ public ResponseEntity handleNotInvokableException(NotInvokableException exception) {
+ return new ResponseEntity<>(HttpStatus.METHOD_NOT_ALLOWED);
+ }
+
+ @ExceptionHandler(ElementNotAFileException.class)
+ public ResponseEntity handleElementNotAFileException(ElementNotAFileException exception) {
+ return new ResponseEntity<>(HttpStatus.PRECONDITION_FAILED);
+ }
+
+ @ExceptionHandler(InsufficientPermissionException.class)
+ public ResponseEntity handleInsufficientPermissionException(InsufficientPermissionException exception, WebRequest request) {
+ return new ResponseEntity<>(HttpStatus.FORBIDDEN);
+ }
+
+ @ExceptionHandler(NullSubjectException.class)
+ public ResponseEntity handleNullSubjectException(NullSubjectException exception) {
+ return new ResponseEntity<>(HttpStatus.UNAUTHORIZED);
+ }
+
+ @ExceptionHandler(OperationDelegationException.class)
+ public ResponseEntity handleNullSubjectException(OperationDelegationException exception) {
+ return new ResponseEntity<>(HttpStatus.FAILED_DEPENDENCY);
+ }
+
+ private ResponseEntity newResultEntity(Exception ex, HttpStatus status) {
+ log.info("Application went into exception {}", ex.getLocalizedMessage());
+ Result result = new Result();
+ Message message = newExceptionMessage(ex.getMessage(), status);
+ result.addMessagesItem(message);
+
+ return ResponseEntity
+ .status(status)
+ .contentType(MediaType.APPLICATION_JSON)
+ .body(result);
+ }
+
+
+ private Message newExceptionMessage(String msg, HttpStatus status) {
+ Message message = new Message();
+ message.setCode("" + status.value());
+ message.setMessageType(MessageTypeEnum.EXCEPTION);
+ message.setTimestamp(OffsetDateTime.now());
+ message.setText(msg);
+ return message;
+ }
+}
\ No newline at end of file
diff --git a/basyx.aasdigitaltwinregistry/basyx.digitaltwinregistry.component/src/main/java/org/eclipse/digitaltwin/basyx/aasdigitaltwinregistry/component/delegate/DiscoveryEnhancedShellDescriptors.java b/basyx.aasdigitaltwinregistry/basyx.digitaltwinregistry.component/src/main/java/org/eclipse/digitaltwin/basyx/aasdigitaltwinregistry/component/delegate/DiscoveryEnhancedShellDescriptors.java
new file mode 100644
index 000000000..ae3abf753
--- /dev/null
+++ b/basyx.aasdigitaltwinregistry/basyx.digitaltwinregistry.component/src/main/java/org/eclipse/digitaltwin/basyx/aasdigitaltwinregistry/component/delegate/DiscoveryEnhancedShellDescriptors.java
@@ -0,0 +1,137 @@
+/*******************************************************************************
+ * 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.aasdigitaltwinregistry.component.delegate;
+
+import jakarta.validation.Valid;
+import org.eclipse.digitaltwin.aas4j.v3.model.impl.DefaultSpecificAssetId;
+import org.eclipse.digitaltwin.aas4j.v3.model.SpecificAssetId;
+import org.eclipse.digitaltwin.basyx.aasdiscoveryservice.backend.mongodb.backend.MongoDBCrudAasDiscovery;
+import org.eclipse.digitaltwin.basyx.aasregistry.model.*;
+import org.eclipse.digitaltwin.basyx.aasregistry.service.api.ShellDescriptorsApiDelegate;
+import org.eclipse.digitaltwin.basyx.aasregistry.service.storage.AasRegistryStorage;
+import org.springframework.beans.factory.ObjectProvider;
+import org.springframework.beans.factory.annotation.Autowired;
+import org.springframework.context.annotation.Primary;
+import org.springframework.http.ResponseEntity;
+import org.springframework.stereotype.Component;
+import java.util.Base64;
+import java.util.List;
+import java.util.stream.Collectors;
+
+@Component
+@Primary
+public class DiscoveryEnhancedShellDescriptors implements ShellDescriptorsApiDelegate {
+
+ @Autowired
+ private MongoDBCrudAasDiscovery mongoDBCrudAasDiscovery;
+
+ private final AasRegistryStorage storage;
+
+ private final ShellDescriptorsApiDelegate originalDelegate;
+
+
+ @Autowired
+ public DiscoveryEnhancedShellDescriptors(
+ AasRegistryStorage storage, ObjectProvider delegateProvider) {
+ this.storage = storage;
+ this.originalDelegate = delegateProvider.stream()
+ .filter(delegate -> !(delegate instanceof DiscoveryEnhancedShellDescriptors))
+ .findFirst()
+ .orElseThrow(() -> new IllegalStateException("No original ShellDescriptorsApiDelegate found"));
+ }
+
+
+ @Override
+ public ResponseEntity getAllAssetAdministrationShellDescriptors(
+ Integer limit, String cursor, AssetKind assetKind, String assetType) {
+ return originalDelegate.getAllAssetAdministrationShellDescriptors(limit, cursor, assetKind, assetType);
+ }
+
+ @Override
+ public ResponseEntity deleteAllShellDescriptors() {
+ ResponseEntity allShells = getAllAssetAdministrationShellDescriptors(null, null, null, null);
+ List allShellIdentifiers = allShells.getBody().getResult();
+ allShellIdentifiers.parallelStream().forEach(assetAdministrationShellDescriptor ->
+ mongoDBCrudAasDiscovery.deleteAllAssetLinksById(Base64.getEncoder().encodeToString(assetAdministrationShellDescriptor.getId().getBytes())));
+ return originalDelegate.deleteAllShellDescriptors();
+ }
+
+ @Override
+ public ResponseEntity deleteAssetAdministrationShellDescriptorById(String aasIdentifier) {
+ return originalDelegate.deleteAssetAdministrationShellDescriptorById(aasIdentifier);
+ }
+
+ @Override
+ public ResponseEntity deleteSubmodelDescriptorByIdThroughSuperpath(String aasIdentifier, String submodelIdentifier) {
+ return originalDelegate.deleteSubmodelDescriptorByIdThroughSuperpath(aasIdentifier, submodelIdentifier);
+ }
+
+ @Override
+ public ResponseEntity getAllSubmodelDescriptorsThroughSuperpath(String aasIdentifier, Integer limit, String cursor) {
+ return originalDelegate.getAllSubmodelDescriptorsThroughSuperpath(aasIdentifier, limit, cursor);
+ }
+
+ @Override
+ public ResponseEntity getAssetAdministrationShellDescriptorById(String aasIdentifier) {
+ return originalDelegate.getAssetAdministrationShellDescriptorById(aasIdentifier);
+ }
+
+ @Override
+ public ResponseEntity getSubmodelDescriptorByIdThroughSuperpath(String aasIdentifier, String submodelIdentifier) {
+ return originalDelegate.getSubmodelDescriptorByIdThroughSuperpath(aasIdentifier, submodelIdentifier);
+ }
+
+ @Override
+ public ResponseEntity postAssetAdministrationShellDescriptor(AssetAdministrationShellDescriptor assetAdministrationShellDescriptor) {
+ String encodedId = Base64.getEncoder().encodeToString(assetAdministrationShellDescriptor.getId().getBytes());
+ @Valid List ids = assetAdministrationShellDescriptor.getSpecificAssetIds();
+ List specificAssetIds = ids.stream()
+ .map(rId -> {
+ SpecificAssetId assetId = new DefaultSpecificAssetId();
+ assetId.setName(rId.getName());
+ assetId.setValue(rId.getValue());
+ return assetId;
+ }).collect(Collectors.toList());
+
+ mongoDBCrudAasDiscovery.createAllAssetLinksById(encodedId, specificAssetIds);
+ return originalDelegate.postAssetAdministrationShellDescriptor(assetAdministrationShellDescriptor);
+ }
+
+ @Override
+ public ResponseEntity postSubmodelDescriptorThroughSuperpath(String aasIdentifier, SubmodelDescriptor submodelDescriptor) {
+ return originalDelegate.postSubmodelDescriptorThroughSuperpath(aasIdentifier, submodelDescriptor);
+ }
+
+ @Override
+ public ResponseEntity putAssetAdministrationShellDescriptorById(String aasIdentifier, AssetAdministrationShellDescriptor assetAdministrationShellDescriptor) {
+ return originalDelegate.putAssetAdministrationShellDescriptorById(aasIdentifier, assetAdministrationShellDescriptor);
+ }
+
+ @Override
+ public ResponseEntity putSubmodelDescriptorByIdThroughSuperpath(String aasIdentifier, String submodelIdentifier, SubmodelDescriptor submodelDescriptor) {
+ return originalDelegate.putSubmodelDescriptorByIdThroughSuperpath(aasIdentifier, submodelIdentifier, submodelDescriptor);
+ }
+}
diff --git a/basyx.aasdigitaltwinregistry/basyx.digitaltwinregistry.component/src/main/resources/application-InMemory.yml b/basyx.aasdigitaltwinregistry/basyx.digitaltwinregistry.component/src/main/resources/application-InMemory.yml
new file mode 100644
index 000000000..b661f2987
--- /dev/null
+++ b/basyx.aasdigitaltwinregistry/basyx.digitaltwinregistry.component/src/main/resources/application-InMemory.yml
@@ -0,0 +1,17 @@
+spring:
+ config:
+ activate:
+ on-profile: InMemory
+ autoconfigure:
+ exclude:
+ - org.springframework.boot.autoconfigure.mongo.MongoAutoConfiguration
+ - org.springframework.boot.autoconfigure.data.mongo.MongoDataAutoConfiguration
+
+
+basyx:
+ backend: InMemory
+
+registry:
+ type: InMemory
+ discovery:
+ enabled: true
\ No newline at end of file
diff --git a/basyx.aasdigitaltwinregistry/basyx.digitaltwinregistry.component/src/main/resources/application-MongoDB.yml b/basyx.aasdigitaltwinregistry/basyx.digitaltwinregistry.component/src/main/resources/application-MongoDB.yml
new file mode 100644
index 000000000..559041a05
--- /dev/null
+++ b/basyx.aasdigitaltwinregistry/basyx.digitaltwinregistry.component/src/main/resources/application-MongoDB.yml
@@ -0,0 +1,25 @@
+spring:
+ config:
+ activate:
+ on-profile: MongoDB
+ data:
+ mongodb:
+ authentication-database: ${AUTHENTICATION_DATABASE:aasregistry}
+ database: ${AUTHENTICATION_DATABASE:aasregistry}
+ host: ${DATABASE_HOST:localhost}
+ port: ${DATABASE_PORT:27017}
+ username: ${DATABASE_USERNAME:smartsystemhub}
+ password: ${DATABASE_PASSWORD:smartsystemshubdatabaseforfactoryX}
+
+basyx:
+ backend: MongoDB
+ aasdiscoveryservice:
+ mongodb:
+ collectionName: ${AUTHENTICATION_DATABASE:aasregistry}
+
+
+
+registry:
+ type: MongoDB
+ discovery:
+ enabled: true
\ No newline at end of file
diff --git a/basyx.aasdigitaltwinregistry/basyx.digitaltwinregistry.component/src/main/resources/application.yml b/basyx.aasdigitaltwinregistry/basyx.digitaltwinregistry.component/src/main/resources/application.yml
new file mode 100644
index 000000000..0f31ae2b5
--- /dev/null
+++ b/basyx.aasdigitaltwinregistry/basyx.digitaltwinregistry.component/src/main/resources/application.yml
@@ -0,0 +1,41 @@
+basyx:
+ aasdiscoveryservice:
+ cors:
+ allowed-methods: GET,POST,PATCH,DELETE,PUT,OPTIONS,HEAD
+ allowed-origins: '*'
+
+management:
+ endpoints:
+ web:
+ exposure:
+ include: health,metrics,mappings
+
+server:
+ port: 8081
+
+spring:
+ profiles:
+ active: logEvents,${SPRING_PROFILE:MongoDB}
+ main:
+ allow-bean-definition-overriding: true
+ application:
+ name: BaSyx Digital Twin Registry
+
+description:
+ profiles: https://admin-shell.io/aas/API/3/0/AssetAdministrationShellRegistryServiceSpecification/SSP-001,
+ https://admin-shell.io/aas/API/3/0/AssetAdministrationShellRegistryServiceSpecification/SSP-002,
+ https://admin-shell.io/aas/API/3/0/DiscoveryServiceSpecification/SSP-001
+
+logging:
+ level:
+ root: ${LOGGING_LEVEL:INFO}
+ org.eclipse.digitaltwin.basyx: ${LOGGING_LEVEL:INFO}
+
+springdoc:
+ api-docs:
+ enabled: true
+ swagger-ui:
+ enabled: true
+ path: /swagger-ui.html
+ csrf:
+ enabled: false
\ No newline at end of file
diff --git a/basyx.aasdigitaltwinregistry/basyx.digitaltwinregistry.component/src/test/java/org/eclipse/digitaltwin/basyx/aasdigitaltwinregistry/component/test/DigitalTwinRegistryTests.java b/basyx.aasdigitaltwinregistry/basyx.digitaltwinregistry.component/src/test/java/org/eclipse/digitaltwin/basyx/aasdigitaltwinregistry/component/test/DigitalTwinRegistryTests.java
new file mode 100644
index 000000000..d3f50cb3f
--- /dev/null
+++ b/basyx.aasdigitaltwinregistry/basyx.digitaltwinregistry.component/src/test/java/org/eclipse/digitaltwin/basyx/aasdigitaltwinregistry/component/test/DigitalTwinRegistryTests.java
@@ -0,0 +1,36 @@
+/*******************************************************************************
+ * 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.aasdigitaltwinregistry.component.test;
+
+import org.junit.Test;
+import org.springframework.boot.test.context.SpringBootTest;
+
+@SpringBootTest
+public class DigitalTwinRegistryTests {
+
+ @Test
+ public void contextLoads() {}
+}
diff --git a/basyx.aasdigitaltwinregistry/basyx.digitaltwinregistry.component/src/test/java/org/eclipse/digitaltwin/basyx/aasdigitaltwinregistry/component/test/configuration/BeanExclusionConfigTest.java b/basyx.aasdigitaltwinregistry/basyx.digitaltwinregistry.component/src/test/java/org/eclipse/digitaltwin/basyx/aasdigitaltwinregistry/component/test/configuration/BeanExclusionConfigTest.java
new file mode 100644
index 000000000..e7ef32ffa
--- /dev/null
+++ b/basyx.aasdigitaltwinregistry/basyx.digitaltwinregistry.component/src/test/java/org/eclipse/digitaltwin/basyx/aasdigitaltwinregistry/component/test/configuration/BeanExclusionConfigTest.java
@@ -0,0 +1,123 @@
+/*******************************************************************************
+ * 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.aasdigitaltwinregistry.component.test.configuration;
+
+import lombok.extern.slf4j.Slf4j;
+import org.eclipse.digitaltwin.basyx.aasdigitaltwinregistry.component.configuration.BeanExclusionConfig;
+import org.junit.Test;
+import org.springframework.beans.factory.support.DefaultListableBeanFactory;
+import org.springframework.beans.factory.support.RootBeanDefinition;
+import org.springframework.context.annotation.AnnotationConfigApplicationContext;
+
+import static org.junit.Assert.*;
+
+@Slf4j
+public class BeanExclusionConfigTest {
+
+ @Test
+ public void testRemoveDuplicateControllersWhenBeanExistsRemovesBeanDefinition() {
+ log.info("Started unit test - testRemoveDuplicateControllersWhenBeanExistsRemovesBeanDefinition()");
+ AnnotationConfigApplicationContext context = new AnnotationConfigApplicationContext();
+ context.register(BeanExclusionConfig.class);
+ DefaultListableBeanFactory beanFactory = context.getDefaultListableBeanFactory();
+ beanFactory.registerBeanDefinition("descriptionApiController", new RootBeanDefinition(Object.class));
+ beanFactory.registerBeanDefinition("searchApiController", new RootBeanDefinition(Object.class));
+ context.refresh();
+ assertFalse("descriptionApiController should be removed", beanFactory.containsBean("descriptionApiController"));
+ assertFalse("searchApiController should be removed", beanFactory.containsBean("searchApiController"));
+ context.close();
+ log.info("Successfully conducted unit test");
+ }
+
+ @Test
+ public void testRemoveDuplicateControllersWhenBeanDoesNotExistNoExceptionThrown() {
+ log.info("Started unit test - testRemoveDuplicateControllersWhenBeanDoesNotExistNoExceptionThrown()");
+ AnnotationConfigApplicationContext context = new AnnotationConfigApplicationContext();
+ context.register(BeanExclusionConfig.class);
+ DefaultListableBeanFactory beanFactory = context.getDefaultListableBeanFactory();
+ context.refresh();
+ assertFalse("descriptionApiController should not exist", beanFactory.containsBean("descriptionApiController"));
+ assertFalse("searchApiController should not exist", beanFactory.containsBean("searchApiController"));
+ context.close();
+ log.info("Successfully conducted unit test");
+ }
+
+ @Test
+ public void testRemoveDuplicateControllersRemovesAllSpecifiedBeans() {
+ log.info("Started unit test - testRemoveDuplicateControllersRemovesAllSpecifiedBeans()");
+ AnnotationConfigApplicationContext context = new AnnotationConfigApplicationContext();
+ context.register(BeanExclusionConfig.class);
+ DefaultListableBeanFactory beanFactory = context.getDefaultListableBeanFactory();
+ String[] duplicateBeans = {
+ "descriptionApiController",
+ "searchApiController",
+ "shellDescriptorsApiController",
+ "homeController",
+ "registryEventLogSink"
+ };
+
+ for (String beanName : duplicateBeans) {
+ beanFactory.registerBeanDefinition(beanName, new RootBeanDefinition(Object.class));
+ }
+
+ context.refresh();
+
+ for (String beanName : duplicateBeans) {
+ assertFalse(beanName + " should be removed", beanFactory.containsBean(beanName));
+ }
+
+ context.close();
+ log.info("Unit Test conducted successfully");
+ }
+
+ @Test
+ public void testRemoveDuplicateControllersDoesNotRemoveOtherBeans() {
+ log.info("Started unit test - testRemoveDuplicateControllersDoesNotRemoveOtherBeans()" );
+ AnnotationConfigApplicationContext context = new AnnotationConfigApplicationContext();
+ context.register(BeanExclusionConfig.class);
+ DefaultListableBeanFactory beanFactory = context.getDefaultListableBeanFactory();
+ beanFactory.registerBeanDefinition("descriptionApiController", new RootBeanDefinition(Object.class));
+ beanFactory.registerBeanDefinition("validServiceBean", new RootBeanDefinition(Object.class));
+ beanFactory.registerBeanDefinition("searchApiController", new RootBeanDefinition(Object.class));
+ beanFactory.registerBeanDefinition("anotherValidBean", new RootBeanDefinition(Object.class));
+ context.refresh();
+ assertFalse("descriptionApiController should be removed", beanFactory.containsBean("descriptionApiController"));
+ assertFalse("searchApiController should be removed", beanFactory.containsBean("searchApiController"));
+ assertTrue("validServiceBean should remain", beanFactory.containsBean("validServiceBean"));
+ assertTrue("anotherValidBean should remain", beanFactory.containsBean("anotherValidBean"));
+ context.close();
+ log.info("Unit test conducted successfully");
+ }
+
+ @Test
+ public void testBeanExclusionConfigBeanFactoryPostProcessorIsStatic() {
+ log.info("Started unit test - testBeanExclusionConfigBeanFactoryPostProcessorIsStatic()");
+ AnnotationConfigApplicationContext context = new AnnotationConfigApplicationContext(BeanExclusionConfig.class);
+ assertTrue("Context should be active", context.isActive());
+ context.close();
+ log.info("Unit test conducted successfully");
+ }
+}
diff --git a/basyx.aasdigitaltwinregistry/basyx.digitaltwinregistry.component/src/test/java/org/eclipse/digitaltwin/basyx/aasdigitaltwinregistry/component/test/controllerAdvice/ControllerAdviceTests.java b/basyx.aasdigitaltwinregistry/basyx.digitaltwinregistry.component/src/test/java/org/eclipse/digitaltwin/basyx/aasdigitaltwinregistry/component/test/controllerAdvice/ControllerAdviceTests.java
new file mode 100644
index 000000000..519ad6fbb
--- /dev/null
+++ b/basyx.aasdigitaltwinregistry/basyx.digitaltwinregistry.component/src/test/java/org/eclipse/digitaltwin/basyx/aasdigitaltwinregistry/component/test/controllerAdvice/ControllerAdviceTests.java
@@ -0,0 +1,207 @@
+/*******************************************************************************
+ * 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.aasdigitaltwinregistry.component.test.controllerAdvice;
+
+import static org.junit.Assert.assertEquals;
+import static org.junit.Assert.assertNotNull;
+
+import lombok.extern.slf4j.Slf4j;
+import org.eclipse.digitaltwin.basyx.aasdigitaltwinregistry.component.controllerAdvice.GlobalExceptionHandler;
+import org.eclipse.digitaltwin.basyx.aasregistry.model.Message;
+import org.eclipse.digitaltwin.basyx.aasregistry.model.Result;
+import org.eclipse.digitaltwin.basyx.core.exceptions.*;
+import org.junit.Before;
+import org.junit.Test;
+import org.springframework.http.HttpStatus;
+import org.springframework.http.ResponseEntity;
+import org.springframework.web.server.ResponseStatusException;
+import org.springframework.web.bind.MethodArgumentNotValidException;
+import org.springframework.validation.ObjectError;
+
+@Slf4j
+public class ControllerAdviceTests {
+
+ private GlobalExceptionHandler advice;
+
+ @Before
+ public void setUp() {
+ advice = new GlobalExceptionHandler();
+ }
+
+ @Test
+ public void testHandleValidationException() {
+ log.info("Started unit test - testHandleValidationException");
+ MethodArgumentNotValidException ex = new MethodArgumentNotValidException(null,
+ new org.springframework.validation.BeanPropertyBindingResult(new Object(), "object"));
+ ex.getBindingResult().addError(new ObjectError("field", "Invalid value"));
+
+ ResponseEntity response = advice.handleValidationException(ex);
+
+ assertEquals(HttpStatus.BAD_REQUEST, response.getStatusCode());
+ assertNotNull(response.getBody());
+ assertEquals(1, response.getBody().getMessages().size());
+ Message msg = response.getBody().getMessages().get(0);
+ assertEquals("EXCEPTION", msg.getMessageType().name());
+ assertNotNull(msg.getTimestamp());
+ log.info("Successfully conducted unit test");
+ }
+
+ @Test
+ public void testHandleResponseStatusException() {
+ log.info("Started unit test - testHandleResponseStatusException");
+ ResponseStatusException ex = new ResponseStatusException(HttpStatus.NOT_FOUND, "Not Found");
+ ResponseEntity response = advice.handleExceptions(ex);
+
+ assertEquals(HttpStatus.NOT_FOUND, response.getStatusCode());
+ assertNotNull(response.getBody());
+ assertEquals("404", response.getBody().getMessages().get(0).getCode());
+ log.info("Successfully conducted unit test");
+ }
+
+ @Test
+ public void testHandleGenericException() {
+ log.info("Started unit test - testHandleGenericException");
+ Exception ex = new Exception("Internal error");
+ ResponseEntity response = advice.handleExceptions(ex);
+
+ assertEquals(HttpStatus.INTERNAL_SERVER_ERROR, response.getStatusCode());
+ assertEquals("500", response.getBody().getMessages().get(0).getCode());
+ assertEquals("Internal error", response.getBody().getMessages().get(0).getText());
+ log.info("Successfully conducted unit test");
+ }
+
+ @Test
+ public void testHandleElementDoesNotExistException() {
+ log.info("Started unit test - testHandleElementDoesNotExistException");
+ ResponseEntity> response = advice.handleElementNotFoundException(new ElementDoesNotExistException(""), null);
+ assertEquals(HttpStatus.NOT_FOUND, response.getStatusCode());
+ log.info("Successfully conducted unit test");
+ }
+
+ @Test
+ public void testHandleAssetLinkDoesNotExistException() {
+ log.info("Started unit test - testHandleAssetLinkDoesNotExistException");
+ ResponseEntity> response = advice.handleElementNotFoundException(new AssetLinkDoesNotExistException(""), null);
+ assertEquals(HttpStatus.NOT_FOUND, response.getStatusCode());
+ log.info("Successfully conducted unit test");
+ }
+
+ @Test
+ public void testHandleFileDoesNotExistException() {
+ log.info("Started unit test - testHandleFileDoesNotExistException");
+ ResponseEntity> response = advice.handleElementNotFoundException(new FileDoesNotExistException(""), null);
+ assertEquals(HttpStatus.NOT_FOUND, response.getStatusCode());
+ log.info("Successfully conducted unit test");
+ }
+
+ @Test
+ public void testHandleCollidingIdentifierException() {
+ log.info("Started unit test - testHandleFileDoesNotExistException");
+ ResponseEntity> response = advice.handleCollidingIdentifierException(new CollidingIdentifierException(""), null);
+ assertEquals(HttpStatus.CONFLICT, response.getStatusCode());
+ log.info("Successfully conducted unit test");
+ }
+
+ @Test
+ public void testHandleMissingIdentifierException() {
+ log.info("Started unit test - testHandleMissingIdentifierException");
+ ResponseEntity> response = advice.handleMissingIdentifierException(new MissingIdentifierException(""), null);
+ assertEquals(HttpStatus.BAD_REQUEST, response.getStatusCode());
+ log.info("Successfully conducted unit test");
+ }
+
+ @Test
+ public void testHandleCollidingAssetLinkException() {
+ log.info("Started unit test - testHandleCollidingAssetLinkException");
+ ResponseEntity> response = advice.handleCollidingIdentifierException(new CollidingAssetLinkException(""), null);
+ assertEquals(HttpStatus.CONFLICT, response.getStatusCode());
+ log.info("Successfully conducted unit test");
+ }
+
+ @Test
+ public void testHandleIllegalArgumentException() {
+ log.info("Started unit test - testHandleIllegalArgumentException");
+ ResponseEntity> response = advice.handleIllegalArgumentException(new IllegalArgumentException());
+ assertEquals(HttpStatus.BAD_REQUEST, response.getStatusCode());
+ log.info("Successfully conducted unit test");
+ }
+
+ @Test
+ public void testHandleIdentificationMismatchException() {
+ log.info("Started unit test - testHandleIdentificationMismatchException");
+ ResponseEntity> response = advice.handleIdMismatchException(new IdentificationMismatchException(""));
+ assertEquals(HttpStatus.BAD_REQUEST, response.getStatusCode());
+ log.info("Successfully conducted unit test");
+ }
+
+ @Test
+ public void testHandleFeatureNotSupportedException() {
+ log.info("Started unit test - testHandleFeatureNotSupportedException");
+ ResponseEntity> response = advice.handleFeatureNotSupportedException(new FeatureNotSupportedException(""));
+ assertEquals(HttpStatus.NOT_IMPLEMENTED, response.getStatusCode());
+ log.info("Successfully conducted unit test");
+ }
+
+ @Test
+ public void testHandleNotInvokableException() {
+ log.info("Started unit test - testHandleNotInvokableException");
+ ResponseEntity> response = advice.handleNotInvokableException(new NotInvokableException(""));
+ assertEquals(HttpStatus.METHOD_NOT_ALLOWED, response.getStatusCode());
+ log.info("Successfully conducted unit test");
+ }
+
+ @Test
+ public void testHandleElementNotAFileException() {
+ log.info("Started unit test - testHandleElementNotAFileException");
+ ResponseEntity> response = advice.handleElementNotAFileException(new ElementNotAFileException(""));
+ assertEquals(HttpStatus.PRECONDITION_FAILED, response.getStatusCode());
+ log.info("Successfully conducted unit test");
+ }
+
+ @Test
+ public void testHandleInsufficientPermissionException() {
+ log.info("Started unit test - testHandleInsufficientPermissionException");
+ ResponseEntity> response = advice.handleInsufficientPermissionException(new InsufficientPermissionException(""), null);
+ assertEquals(HttpStatus.FORBIDDEN, response.getStatusCode());
+ log.info("Successfully conducted unit test");
+ }
+
+ @Test
+ public void testHandleNullSubjectException() {
+ log.info("Started unit test - testHandleNullSubjectException");
+ ResponseEntity> response = advice.handleNullSubjectException(new NullSubjectException(""));
+ assertEquals(HttpStatus.UNAUTHORIZED, response.getStatusCode());
+ log.info("Successfully conducted unit test");
+ }
+
+ @Test
+ public void testHandleOperationDelegationException() {
+ log.info("Started unit test - testHandleOperationDelegationException");
+ ResponseEntity> response = advice.handleNullSubjectException(new OperationDelegationException(""));
+ assertEquals(HttpStatus.FAILED_DEPENDENCY, response.getStatusCode());
+ log.info("Successfully conducted unit test");
+ }
+}
\ No newline at end of file
diff --git a/basyx.aasdigitaltwinregistry/pom.xml b/basyx.aasdigitaltwinregistry/pom.xml
new file mode 100644
index 000000000..723c8e9e8
--- /dev/null
+++ b/basyx.aasdigitaltwinregistry/pom.xml
@@ -0,0 +1,30 @@
+
+
+ 4.0.0
+
+
+ org.eclipse.digitaltwin.basyx
+ basyx.parent
+ ${revision}
+
+
+
+ 17
+ ${java.version}
+ ${java.version}
+ false
+ false
+
+
+ basyx.digitaltwinregistry
+ BaSyx Digital Twin Registry
+ BaSyx Digital Twin Registry
+ pom
+
+
+ basyx.digitaltwinregistry.component
+
+
+
\ No newline at end of file
diff --git a/basyx.aasdiscoveryservice/basyx.aasdiscoveryservice.component/src/main/resources/application.properties b/basyx.aasdiscoveryservice/basyx.aasdiscoveryservice.component/src/main/resources/application.properties
index e8d8184c9..da8db38d0 100644
--- a/basyx.aasdiscoveryservice/basyx.aasdiscoveryservice.component/src/main/resources/application.properties
+++ b/basyx.aasdiscoveryservice/basyx.aasdiscoveryservice.component/src/main/resources/application.properties
@@ -34,4 +34,4 @@ basyx.backend=InMemory
# Disable the Swagger UI
####################################################################################
#springdoc.swagger-ui.enabled=false
-#springdoc.api-docs.enabled=false
+#springdoc.api-docs.enabled=false
\ No newline at end of file
diff --git a/pom.xml b/pom.xml
index 9cd66815f..8035ff068 100644
--- a/pom.xml
+++ b/pom.xml
@@ -1511,4 +1511,4 @@
-
+
\ No newline at end of file