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