diff --git a/.github/workflows/basyx_test.yml b/.github/workflows/basyx_test.yml
index 27e3943b7..c67070f00 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 6955f66be..2f0dc8c94 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: 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 7c6d7c100..e4eef8337 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: 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..3f155b0c3
--- /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 |
\ No newline at end of file
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..082d66394
--- /dev/null
+++ b/basyx.aasdigitaltwinregistry/basyx.digitaltwinregistry.component/pom.xml
@@ -0,0 +1,224 @@
+
+
+ 4.0.0
+
+
+ org.eclipse.digitaltwin.basyx
+ aasdigitaltwinregistry
+ ${revision}
+
+
+ basyx.digitaltwinregistry.component
+ BaSyx Digital Twin Registry Component
+ BaSyx Digital Twin Registry Component
+
+
+
+ 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
+ aasregistry-feature-discovery-integration
+ ${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.eclipse.digitaltwin.basyx
+ aasregistry-feature-discovery-integration
+ 2.0.0-SNAPSHOT
+ compile
+
+
+
+
+
+
+ org.springframework.boot
+ spring-boot-maven-plugin
+
+ org.eclipse.digitaltwin.basyx.digitaltwinregistry.component.DigitalTwinRegistry
+
+
+
+
+ repackage
+
+
+
+
+
+
+
\ No newline at end of file
diff --git a/basyx.aasdigitaltwinregistry/basyx.digitaltwinregistry.component/src/main/java/org/eclipse/digitaltwin/basyx/digitaltwinregistry/component/DigitalTwinRegistry.java b/basyx.aasdigitaltwinregistry/basyx.digitaltwinregistry.component/src/main/java/org/eclipse/digitaltwin/basyx/digitaltwinregistry/component/DigitalTwinRegistry.java
new file mode 100644
index 000000000..e36029bb2
--- /dev/null
+++ b/basyx.aasdigitaltwinregistry/basyx.digitaltwinregistry.component/src/main/java/org/eclipse/digitaltwin/basyx/digitaltwinregistry/component/DigitalTwinRegistry.java
@@ -0,0 +1,108 @@
+/*******************************************************************************
+ * Copyright (C) 2025 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.digitaltwinregistry.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/digitaltwinregistry/component/controllerAdvice/GlobalExceptionHandler.java b/basyx.aasdigitaltwinregistry/basyx.digitaltwinregistry.component/src/main/java/org/eclipse/digitaltwin/basyx/digitaltwinregistry/component/controllerAdvice/GlobalExceptionHandler.java
new file mode 100644
index 000000000..ca22a60e0
--- /dev/null
+++ b/basyx.aasdigitaltwinregistry/basyx.digitaltwinregistry.component/src/main/java/org/eclipse/digitaltwin/basyx/digitaltwinregistry/component/controllerAdvice/GlobalExceptionHandler.java
@@ -0,0 +1,173 @@
+/*******************************************************************************
+ * Copyright (C) 2025 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.digitaltwinregistry.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/digitaltwinregistry/component/documentation/DTRegistryApiDocumentationConfiguration.java b/basyx.aasdigitaltwinregistry/basyx.digitaltwinregistry.component/src/main/java/org/eclipse/digitaltwin/basyx/digitaltwinregistry/component/documentation/DTRegistryApiDocumentationConfiguration.java
new file mode 100644
index 000000000..65142dec8
--- /dev/null
+++ b/basyx.aasdigitaltwinregistry/basyx.digitaltwinregistry.component/src/main/java/org/eclipse/digitaltwin/basyx/digitaltwinregistry/component/documentation/DTRegistryApiDocumentationConfiguration.java
@@ -0,0 +1,55 @@
+/*******************************************************************************
+ * Copyright (C) 2025 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.digitaltwinregistry.component.documentation;
+
+import io.swagger.v3.oas.models.OpenAPI;
+import org.eclipse.digitaltwin.basyx.http.documentation.RepositoryApiDocumentationConfiguration;
+import org.springframework.context.annotation.Bean;
+import org.springframework.context.annotation.Configuration;
+import io.swagger.v3.oas.models.info.Info;
+import org.springframework.context.annotation.Primary;
+
+@Configuration
+public class DTRegistryApiDocumentationConfiguration extends RepositoryApiDocumentationConfiguration {
+
+ private static final String TITLE = "BaSyx Digital Twin Registry";
+ private static final String DESCRIPTION = "BaSyx Digital Twin Registry API";
+
+ @Bean
+ @Primary
+ public OpenAPI customOpenAPI() {
+ return new OpenAPI().info(apiInfo());
+ }
+
+ @Override
+ protected Info apiInfo() {
+ return new Info().title(TITLE)
+ .description(DESCRIPTION)
+ .version(VERSION)
+ .contact(apiContact())
+ .license(apiLicence());
+ }
+}
\ No newline at end of file
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..2a14ad337
--- /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:db-name}
+ database: ${AUTHENTICATION_DATABASE:db-name}
+ host: ${DATABASE_HOST:localhost}
+ port: ${DATABASE_PORT:27017}
+ username: ${DATABASE_USERNAME:db-username}
+ password: ${DATABASE_PASSWORD:db-password}
+
+basyx:
+ backend: MongoDB
+ aasdiscoveryservice:
+ mongodb:
+ collectionName: ${AUTHENTICATION_DATABASE:db-name}
+
+
+
+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..c84a8fafc
--- /dev/null
+++ b/basyx.aasdigitaltwinregistry/basyx.digitaltwinregistry.component/src/main/resources/application.yml
@@ -0,0 +1,48 @@
+basyx:
+ aasdiscoveryservice:
+ cors:
+ allowed-methods: GET,POST,PATCH,DELETE,PUT,OPTIONS,HEAD
+ allowed-origins: '*'
+
+
+ aasregistry:
+ feature:
+ discoveryintegration:
+ enabled: true
+ baseUrl: http://localhost:8081
+
+ endpoints:
+ web:
+ exposure:
+ include: health,metrics,mappings
+
+server:
+ port: 8081
+
+spring:
+ profiles:
+ active: logEvents,${SPRING_PROFILE:InMemory}
+ 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/digitaltwinregistry/component/tests/DigitalTwinRegistryTests.java b/basyx.aasdigitaltwinregistry/basyx.digitaltwinregistry.component/src/test/java/org/eclipse/digitaltwin/basyx/digitaltwinregistry/component/tests/DigitalTwinRegistryTests.java
new file mode 100644
index 000000000..4173480e9
--- /dev/null
+++ b/basyx.aasdigitaltwinregistry/basyx.digitaltwinregistry.component/src/test/java/org/eclipse/digitaltwin/basyx/digitaltwinregistry/component/tests/DigitalTwinRegistryTests.java
@@ -0,0 +1,36 @@
+/*******************************************************************************
+ * Copyright (C) 2025 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.digitaltwinregistry.component.tests;
+
+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/digitaltwinregistry/component/tests/controllerAdvice/GlobalExceptionHandlerTest.java b/basyx.aasdigitaltwinregistry/basyx.digitaltwinregistry.component/src/test/java/org/eclipse/digitaltwin/basyx/digitaltwinregistry/component/tests/controllerAdvice/GlobalExceptionHandlerTest.java
new file mode 100644
index 000000000..c1fa80223
--- /dev/null
+++ b/basyx.aasdigitaltwinregistry/basyx.digitaltwinregistry.component/src/test/java/org/eclipse/digitaltwin/basyx/digitaltwinregistry/component/tests/controllerAdvice/GlobalExceptionHandlerTest.java
@@ -0,0 +1,270 @@
+/*******************************************************************************
+ * Copyright (C) 2025 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.digitaltwinregistry.component.tests.controllerAdvice;
+
+
+import static org.junit.Assert.assertEquals;
+import static org.junit.Assert.assertNotNull;
+import static org.mockito.Mockito.mock;
+import static org.mockito.Mockito.when;
+
+import java.util.Arrays;
+import java.util.List;
+
+import lombok.extern.slf4j.Slf4j;
+import org.eclipse.digitaltwin.basyx.digitaltwinregistry.component.controllerAdvice.GlobalExceptionHandler;
+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.junit.Before;
+import org.junit.Test;
+import org.springframework.http.HttpStatus;
+import org.springframework.http.ResponseEntity;
+import org.springframework.validation.ObjectError;
+import org.springframework.web.bind.MethodArgumentNotValidException;
+import org.springframework.web.context.request.WebRequest;
+
+@Slf4j
+public class GlobalExceptionHandlerTest {
+
+ private GlobalExceptionHandler exceptionHandler;
+ private WebRequest mockWebRequest;
+
+ @Before
+ public void setUp() {
+ exceptionHandler = new GlobalExceptionHandler();
+ mockWebRequest = mock(WebRequest.class);
+ }
+
+ @Test
+ public void testHandleValidationException() {
+ log.info("Started unit test - testHandleValidationException()");
+ MethodArgumentNotValidException ex = mock(MethodArgumentNotValidException.class);
+ ObjectError error1 = new ObjectError("object", "Error message 1");
+ ObjectError error2 = new ObjectError("object", "Error message 2");
+ List errors = Arrays.asList(error1, error2);
+ when(ex.getAllErrors()).thenReturn(errors);
+ ResponseEntity response = exceptionHandler.handleValidationException(ex);
+ assertEquals(HttpStatus.BAD_REQUEST, response.getStatusCode());
+ assertNotNull(response.getBody());
+ assertNotNull(response.getBody().getMessages());
+ assertEquals(2, response.getBody().getMessages().size());
+ log.info("Unit test conducted successfully");
+ }
+
+ @Test
+ public void testHandleGenericException() {
+ log.info("Started unit test - testHandleGenericException()");
+ Exception ex = new Exception("Generic error");
+ ResponseEntity response = exceptionHandler.handleExceptions(ex);
+ assertEquals(HttpStatus.INTERNAL_SERVER_ERROR, response.getStatusCode());
+ assertResultHasExceptionMessage(response.getBody(), "Generic error");
+ log.info("Unit test conducted successfully");
+ }
+
+ @Test
+ public void testHandleElementDoesNotExistException() {
+ log.info("Started unit test - testHandleElementDoesNotExistException()");
+ ElementDoesNotExistException ex = new ElementDoesNotExistException("Element not found");
+ ResponseEntity