From 2da9c43c992ff4a269bcdd90d118568c2e0305b4 Mon Sep 17 00:00:00 2001 From: Hardi Shah Date: Mon, 3 Aug 2026 16:44:38 +0530 Subject: [PATCH 01/25] Add superpath endpoints for AAS submodel navigation Introduce new REST endpoints for accessing submodel descriptors, submodels, and submodel elements via AAS identifiers. Update controllers, handlers, services, and DTOs to support these routes. Add validation to ensure submodels belong to the specified AAS. Extend unit and integration tests, dependency injection, and test definitions (.bru) for the new functionality. Improves API discoverability and AAS API conformance. --- .../Aas Registry/Get All ShellDescriptors.bru | 2 +- ...Get Submodel by submodelId and shellId.bru | 26 ++++ ...Get Submodel by submodelId and shellId.bru | 26 ++++ example/apiCollection/collection.bru | 4 + .../apiCollection/environments/local-net.bru | 3 + .../ShellDescriptorControllerTests.cs | 100 ++++++++++++++ .../AasRepositoryControllerTests.cs | 98 ++++++++++++++ .../Handler/ShellDescriptorHandlerTests.cs | 103 ++++++++++++++- .../ShellDescriptorControllerTests.cs | 38 ++++++ .../AasRepositoryControllerTests.cs | 62 ++++++++- .../Handler/AasRepositoryHandlerTests.cs | 124 +++++++++++++++++- .../Handler/IShellDescriptorHandler.cs | 5 + .../Handler/ShellDescriptorHandler.cs | 62 ++++++++- .../GetSubmodelDescriptorByAasRequest.cs | 3 + .../GetSubmodelDescriptorsByAasRequest.cs | 3 + .../AasRegistry/ShellDescriptorController.cs | 55 ++++++++ .../AasRepository/AasRepositoryController.cs | 98 ++++++++++++++ .../Handler/AasRepositoryHandler.cs | 62 ++++++++- .../Handler/IAasRepositoryHandler.cs | 7 + .../GetAllSubmodelElementsByAasRequest.cs | 11 ++ .../Requests/GetSubmodelByAasRequest.cs | 5 + .../GetSubmodelElementByAasRequest.cs | 5 + .../AasRegistry/IShellDescriptorService.cs | 5 + .../AasRegistry/ShellDescriptorService.cs | 84 +++++++++++- .../AasRepository/AasRepositoryService.cs | 51 ++++++- .../AasRepository/IAasRepositoryService.cs | 10 ++ 26 files changed, 1037 insertions(+), 15 deletions(-) create mode 100644 example/apiCollection/Aas Repository/Product1/Get Submodel by submodelId and shellId.bru create mode 100644 example/apiCollection/Aas Repository/Product2/Get Submodel by submodelId and shellId.bru create mode 100644 example/apiCollection/environments/local-net.bru create mode 100644 source/AAS.TwinEngine.DataEngine/Api/AasRegistry/Requests/GetSubmodelDescriptorByAasRequest.cs create mode 100644 source/AAS.TwinEngine.DataEngine/Api/AasRegistry/Requests/GetSubmodelDescriptorsByAasRequest.cs create mode 100644 source/AAS.TwinEngine.DataEngine/Api/AasRepository/Requests/GetAllSubmodelElementsByAasRequest.cs create mode 100644 source/AAS.TwinEngine.DataEngine/Api/AasRepository/Requests/GetSubmodelByAasRequest.cs create mode 100644 source/AAS.TwinEngine.DataEngine/Api/AasRepository/Requests/GetSubmodelElementByAasRequest.cs diff --git a/example/apiCollection/Aas Registry/Get All ShellDescriptors.bru b/example/apiCollection/Aas Registry/Get All ShellDescriptors.bru index 1fc57910..1adb4ca5 100644 --- a/example/apiCollection/Aas Registry/Get All ShellDescriptors.bru +++ b/example/apiCollection/Aas Registry/Get All ShellDescriptors.bru @@ -5,7 +5,7 @@ meta { } get { - url: {{DataEngineBaseUrl}}/shell-descriptors?limit&cursor + url: {{DataEngineBaseUrl}}/shell-descriptors?limit=&cursor= body: none auth: inherit } diff --git a/example/apiCollection/Aas Repository/Product1/Get Submodel by submodelId and shellId.bru b/example/apiCollection/Aas Repository/Product1/Get Submodel by submodelId and shellId.bru new file mode 100644 index 00000000..fe76a11f --- /dev/null +++ b/example/apiCollection/Aas Repository/Product1/Get Submodel by submodelId and shellId.bru @@ -0,0 +1,26 @@ +meta { + name: Get Submodel by submodelId and shellId + type: http + seq: 5 +} + +get { + url: {{DataEngineBaseUrl}}/shells/:aasIdentifier/submodels/:submodelIdentifier + body: none + auth: inherit +} + +params:query { + ~level: + ~extent: +} + +params:path { + submodelIdentifier: {{submodelIdentifierCarbonFootprint-1}} + aasIdentifier: {{aasIdentifier-1}} +} + +settings { + encodeUrl: true + timeout: 0 +} diff --git a/example/apiCollection/Aas Repository/Product2/Get Submodel by submodelId and shellId.bru b/example/apiCollection/Aas Repository/Product2/Get Submodel by submodelId and shellId.bru new file mode 100644 index 00000000..9ef0ea40 --- /dev/null +++ b/example/apiCollection/Aas Repository/Product2/Get Submodel by submodelId and shellId.bru @@ -0,0 +1,26 @@ +meta { + name: Get Submodel by submodelId and shellId + type: http + seq: 6 +} + +get { + url: {{DataEngineBaseUrl}}/shells/:aasIdentifier/submodels/:submodelIdentifier + body: none + auth: inherit +} + +params:query { + ~level: + ~extent: +} + +params:path { + submodelIdentifier: {{submodelIdentifierNameplate-2}} + aasIdentifier: {{aasIdentifier-2}} +} + +settings { + encodeUrl: true + timeout: 0 +} diff --git a/example/apiCollection/collection.bru b/example/apiCollection/collection.bru index 669943d7..f7188433 100644 --- a/example/apiCollection/collection.bru +++ b/example/apiCollection/collection.bru @@ -13,6 +13,8 @@ vars:pre-request { assetId-product1: {"name":"SerialNumber","value":"SN-FMABC1234-9804820"} assetId-product2: {"name":"SerialNumber","value":"SN-FMABC1238-9804821"} idShort: Product2 + submodelIdentifierNameplate-2: https://mm-software.com/submodel/{{productId-2}}/Nameplate + submodelIdentifierNameplate-3: https://mm-software.com/submodel/{{productId-3}}/Nameplate } script:pre-request { @@ -26,6 +28,8 @@ script:pre-request { 'aasIdentifier-3', 'submodelIdentifierMaintenanceInstructions-1', 'submodelIdentifierNameplate-1', + 'submodelIdentifierNameplate-2', + 'submodelIdentifierNameplate-3', 'submodelIdentifierHandoverDocumentation-1', 'submodelIdentifierTechnicalData-1', 'submodelIdentifierCarbonFootprint-1', diff --git a/example/apiCollection/environments/local-net.bru b/example/apiCollection/environments/local-net.bru new file mode 100644 index 00000000..4a42e390 --- /dev/null +++ b/example/apiCollection/environments/local-net.bru @@ -0,0 +1,3 @@ +vars { + DataEngineBaseUrl: https://localhost:5059/ +} diff --git a/source/AAS.TwinEngine.DataEngine.ModuleTests/Api/Services/AasRegistry/ShellDescriptorControllerTests.cs b/source/AAS.TwinEngine.DataEngine.ModuleTests/Api/Services/AasRegistry/ShellDescriptorControllerTests.cs index d66b1f35..9793074e 100644 --- a/source/AAS.TwinEngine.DataEngine.ModuleTests/Api/Services/AasRegistry/ShellDescriptorControllerTests.cs +++ b/source/AAS.TwinEngine.DataEngine.ModuleTests/Api/Services/AasRegistry/ShellDescriptorControllerTests.cs @@ -7,10 +7,14 @@ using AAS.TwinEngine.DataEngine.ApplicationLogic.Services.AasEnvironment.Providers; using AAS.TwinEngine.DataEngine.ApplicationLogic.Services.Plugin; using AAS.TwinEngine.DataEngine.ApplicationLogic.Services.Plugin.Providers; +using AAS.TwinEngine.DataEngine.ApplicationLogic.Services.SubmodelRegistry.Providers; +using AAS.TwinEngine.DataEngine.DomainModel.SubmodelRegistry; using AAS.TwinEngine.DataEngine.Infrastructure.Http.Clients; using AAS.TwinEngine.DataEngine.ModuleTests.Common; using AAS.TwinEngine.DataEngine.ServiceConfiguration.Config; +using AasCore.Aas3_1; + using Microsoft.AspNetCore.WebUtilities; using Microsoft.Extensions.DependencyInjection; @@ -23,12 +27,14 @@ public abstract class ShellDescriptorControllerTests : IDisposable { private readonly ConfigTestFactory _factory; private readonly ITemplateProvider _mockTemplateProvider; + private readonly ISubmodelDescriptorProvider _mockSubmodelDescriptorProvider; private readonly HttpClient _client; private readonly ICreateClient _httpClientFactory; protected ShellDescriptorControllerTests(string configDir) { _mockTemplateProvider = Substitute.For(); + _mockSubmodelDescriptorProvider = Substitute.For(); var mockPluginManifestProvider = Substitute.For(); var mockPluginManifestConflictHandler = Substitute.For(); _httpClientFactory = Substitute.For(); @@ -38,6 +44,7 @@ protected ShellDescriptorControllerTests(string configDir) _ = services.AddSingleton(_httpClientFactory); _ = services.AddSingleton(mockPluginManifestProvider); _ = services.AddSingleton(_mockTemplateProvider); + _ = services.AddSingleton(_mockSubmodelDescriptorProvider); _ = services.AddSingleton(mockPluginManifestConflictHandler); }); @@ -437,6 +444,99 @@ public async Task GetShellDescriptorById_ValidAasIdentifiers_DoesNotReturn400Asy #endregion + [Fact] + public async Task GetAllSubmodelDescriptorsByAasIdAsync_ReturnsOkAsync() + { + // Arrange + const string AasIdentifier = "aHR0cHM6Ly9leGFtcGxlLmNvbS9pZHMvYWFzLzExNzBfMTE2MF8zMDUyXzY1Njg="; + const string SubmodelId = "http://example.com/submodel/Nameplate"; + + var submodelRefs = new List + { + new Reference( + ReferenceTypes.ModelReference, + [new Key(KeyTypes.Submodel, SubmodelId)], + null) + }; + + _ = _mockTemplateProvider.GetSubmodelRefByIdAsync(Arg.Any(), Arg.Any()) + .Returns(submodelRefs); + + _ = _mockSubmodelDescriptorProvider + .GetDataForSubmodelDescriptorByIdAsync(SubmodelId, Arg.Any()) + .Returns(new SubmodelDescriptor { Id = SubmodelId, Endpoints = [] }); + + // Act + var response = await _client.GetAsync($"/shell-descriptors/{AasIdentifier}/submodel-descriptors"); + + // Assert + Assert.Equal(HttpStatusCode.OK, response.StatusCode); + var json = await response.Content.ReadFromJsonAsync(); + Assert.NotNull(json); + var result = json["result"]?.AsArray(); + Assert.NotNull(result); + _ = Assert.Single(result); + } + + [Fact] + public async Task GetSubmodelDescriptorByAasIdAsync_ReturnsOkAsync() + { + // Arrange + const string AasIdentifier = "aHR0cHM6Ly9leGFtcGxlLmNvbS9pZHMvYWFzLzExNzBfMTE2MF8zMDUyXzY1Njg="; + const string SubmodelId = "http://example.com/submodel/Nameplate"; + var encodedSubmodelId = EncodeBase64Url(SubmodelId); + + var submodelRefs = new List + { + new Reference( + ReferenceTypes.ModelReference, + [new Key(KeyTypes.Submodel, SubmodelId)], + null) + }; + + _ = _mockTemplateProvider.GetSubmodelRefByIdAsync(Arg.Any(), Arg.Any()) + .Returns(submodelRefs); + + _ = _mockSubmodelDescriptorProvider + .GetDataForSubmodelDescriptorByIdAsync(SubmodelId, Arg.Any()) + .Returns(new SubmodelDescriptor { Id = SubmodelId, Endpoints = [] }); + + // Act + var response = await _client.GetAsync($"/shell-descriptors/{AasIdentifier}/submodel-descriptors/{encodedSubmodelId}"); + + // Assert + Assert.Equal(HttpStatusCode.OK, response.StatusCode); + var json = await response.Content.ReadFromJsonAsync(); + Assert.NotNull(json); + } + + [Fact] + public async Task GetSubmodelDescriptorByAasIdAsync_WhenSubmodelNotInAas_Returns404Async() + { + // Arrange + const string AasIdentifier = "aHR0cHM6Ly9leGFtcGxlLmNvbS9pZHMvYWFzLzExNzBfMTE2MF8zMDUyXzY1Njg="; + const string OtherSubmodelId = "http://example.com/submodel/Other"; + const string RequestedSubmodelId = "http://example.com/submodel/Missing"; + var encodedSubmodelId = EncodeBase64Url(RequestedSubmodelId); + + var submodelRefs = new List + { + new Reference( + ReferenceTypes.ModelReference, + [new Key(KeyTypes.Submodel, OtherSubmodelId)], + null) + }; + + _ = _mockTemplateProvider.GetSubmodelRefByIdAsync(Arg.Any(), Arg.Any()) + .Returns(submodelRefs); + + // Act + var response = await _client.GetAsync($"/shell-descriptors/{AasIdentifier}/submodel-descriptors/{encodedSubmodelId}"); + + // Assert + Assert.Equal(HttpStatusCode.NotFound, response.StatusCode); + } + private static string EncodeBase64Url(string plainText) { if (string.IsNullOrWhiteSpace(plainText)) diff --git a/source/AAS.TwinEngine.DataEngine.ModuleTests/Api/Services/AasRepository/AasRepositoryControllerTests.cs b/source/AAS.TwinEngine.DataEngine.ModuleTests/Api/Services/AasRepository/AasRepositoryControllerTests.cs index 4178c2bf..93a63d04 100644 --- a/source/AAS.TwinEngine.DataEngine.ModuleTests/Api/Services/AasRepository/AasRepositoryControllerTests.cs +++ b/source/AAS.TwinEngine.DataEngine.ModuleTests/Api/Services/AasRepository/AasRepositoryControllerTests.cs @@ -8,6 +8,9 @@ using AAS.TwinEngine.DataEngine.ApplicationLogic.Services.AasEnvironment.Providers; using AAS.TwinEngine.DataEngine.ApplicationLogic.Services.Plugin; using AAS.TwinEngine.DataEngine.ApplicationLogic.Services.Plugin.Providers; +using AAS.TwinEngine.DataEngine.ApplicationLogic.Services.SubmodelRepository; +using AAS.TwinEngine.DataEngine.DomainModel.Shared; +using AAS.TwinEngine.DataEngine.DomainModel.SubmodelRepository; using AAS.TwinEngine.DataEngine.Infrastructure.Http.Clients; using AAS.TwinEngine.DataEngine.ModuleTests.Common; using AAS.TwinEngine.DataEngine.ServiceConfiguration.Config; @@ -26,12 +29,14 @@ public abstract class AasRepositoryControllerTests : IDisposable { private readonly ConfigTestFactory _factory; private readonly ITemplateProvider _mockTemplateProvider; + private readonly ISubmodelRepositoryService _mockSubmodelRepositoryService; private readonly HttpClient _client; private readonly ICreateClient _httpClientFactory; protected AasRepositoryControllerTests(string configDir) { _mockTemplateProvider = Substitute.For(); + _mockSubmodelRepositoryService = Substitute.For(); var mockPluginManifestProvider = Substitute.For(); var mockPluginManifestConflictHandler = Substitute.For(); _httpClientFactory = Substitute.For(); @@ -42,6 +47,7 @@ protected AasRepositoryControllerTests(string configDir) _ = services.AddSingleton(mockPluginManifestConflictHandler); _ = services.AddSingleton(_httpClientFactory); _ = services.AddSingleton(_mockTemplateProvider); + _ = services.AddSingleton(_mockSubmodelRepositoryService); }); _client = _factory.CreateClient(); @@ -549,6 +555,98 @@ private void SetupTemplateProvider() }); } + [Fact] + public async Task GetSubmodelByAasIdAsync_ReturnsOkAsync() + { + // Arrange + const string AasIdentifier = "aHR0cHM6Ly9leGFtcGxlLmNvbS9pZHMvYWFzLzExNzBfMTE2MF8zMDUyXzY1Njg="; + const string SubmodelId = "http://example.com/submodel/Nameplate"; + var encodedSubmodelId = EncodeBase64Url(SubmodelId); + + _ = _mockTemplateProvider.GetSubmodelRefByIdAsync(Arg.Any(), Arg.Any()) + .Returns([new Reference(ReferenceTypes.ModelReference, [new Key(KeyTypes.Submodel, SubmodelId)], null)]); + + _ = _mockSubmodelRepositoryService + .GetSubmodelByAasIdAsync(SubmodelId, Arg.Any(), Arg.Any()) + .Returns(new Submodel(id: SubmodelId)); + + // Act + var response = await _client.GetAsync($"/shells/{AasIdentifier}/submodels/{encodedSubmodelId}"); + + // Assert + Assert.Equal(HttpStatusCode.OK, response.StatusCode); + var json = await response.Content.ReadFromJsonAsync(); + Assert.NotNull(json); + } + + [Fact] + public async Task GetSubmodelByAasIdAsync_WhenSubmodelNotInAas_Returns404Async() + { + // Arrange + const string AasIdentifier = "aHR0cHM6Ly9leGFtcGxlLmNvbS9pZHMvYWFzLzExNzBfMTE2MF8zMDUyXzY1Njg="; + const string RequestedSubmodelId = "http://example.com/submodel/Missing"; + var encodedSubmodelId = EncodeBase64Url(RequestedSubmodelId); + + _ = _mockTemplateProvider.GetSubmodelRefByIdAsync(Arg.Any(), Arg.Any()) + .Returns([new Reference(ReferenceTypes.ModelReference, [new Key(KeyTypes.Submodel, "http://example.com/submodel/Other")], null)]); + + // Act + var response = await _client.GetAsync($"/shells/{AasIdentifier}/submodels/{encodedSubmodelId}"); + + // Assert + Assert.Equal(HttpStatusCode.NotFound, response.StatusCode); + } + + [Fact] + public async Task GetAllSubmodelElementsByAasIdAsync_ReturnsOkAsync() + { + // Arrange + const string AasIdentifier = "aHR0cHM6Ly9leGFtcGxlLmNvbS9pZHMvYWFzLzExNzBfMTE2MF8zMDUyXzY1Njg="; + const string SubmodelId = "http://example.com/submodel/Nameplate"; + var encodedSubmodelId = EncodeBase64Url(SubmodelId); + + _ = _mockTemplateProvider.GetSubmodelRefByIdAsync(Arg.Any(), Arg.Any()) + .Returns([new Reference(ReferenceTypes.ModelReference, [new Key(KeyTypes.Submodel, SubmodelId)], null)]); + + _ = _mockSubmodelRepositoryService + .GetAllSubmodelElementsAsync(SubmodelId, Arg.Any(), Arg.Any(), Arg.Any(), Arg.Any()) + .Returns(new SubmodelElementsPage { PagingMetaData = new PagingMetaData { Cursor = null }, Result = [] }); + + // Act + var response = await _client.GetAsync($"/shells/{AasIdentifier}/submodels/{encodedSubmodelId}/submodel-elements"); + + // Assert + Assert.Equal(HttpStatusCode.OK, response.StatusCode); + var json = await response.Content.ReadFromJsonAsync(); + Assert.NotNull(json); + Assert.True(json.ContainsKey("result")); + } + + [Fact] + public async Task GetSubmodelElementByAasIdAsync_ReturnsOkAsync() + { + // Arrange + const string AasIdentifier = "aHR0cHM6Ly9leGFtcGxlLmNvbS9pZHMvYWFzLzExNzBfMTE2MF8zMDUyXzY1Njg="; + const string SubmodelId = "http://example.com/submodel/Nameplate"; + const string IdShortPath = "ManufacturerName"; + var encodedSubmodelId = EncodeBase64Url(SubmodelId); + + _ = _mockTemplateProvider.GetSubmodelRefByIdAsync(Arg.Any(), Arg.Any()) + .Returns([new Reference(ReferenceTypes.ModelReference, [new Key(KeyTypes.Submodel, SubmodelId)], null)]); + + _ = _mockSubmodelRepositoryService + .GetSubmodelElementAsync(SubmodelId, IdShortPath, Arg.Any()) + .Returns(new Property(DataTypeDefXsd.String) { IdShort = IdShortPath }); + + // Act + var response = await _client.GetAsync($"/shells/{AasIdentifier}/submodels/{encodedSubmodelId}/submodel-elements/{IdShortPath}"); + + // Assert + Assert.Equal(HttpStatusCode.OK, response.StatusCode); + var json = await response.Content.ReadFromJsonAsync(); + Assert.NotNull(json); + } + private static string EncodeBase64Url(string plainText) { if (string.IsNullOrWhiteSpace(plainText)) diff --git a/source/AAS.TwinEngine.DataEngine.UnitTests/Api/AasRegistry/Handler/ShellDescriptorHandlerTests.cs b/source/AAS.TwinEngine.DataEngine.UnitTests/Api/AasRegistry/Handler/ShellDescriptorHandlerTests.cs index e5ba7655..e2c2119e 100644 --- a/source/AAS.TwinEngine.DataEngine.UnitTests/Api/AasRegistry/Handler/ShellDescriptorHandlerTests.cs +++ b/source/AAS.TwinEngine.DataEngine.UnitTests/Api/AasRegistry/Handler/ShellDescriptorHandlerTests.cs @@ -1,25 +1,36 @@ using AAS.TwinEngine.DataEngine.Api.AasRegistry.Handler; using AAS.TwinEngine.DataEngine.Api.AasRegistry.Requests; using AAS.TwinEngine.DataEngine.Api.AasRegistry.Responses; +using AAS.TwinEngine.DataEngine.Api.SubmodelRegistry.Responses; using AAS.TwinEngine.DataEngine.ApplicationLogic.Exceptions.Application; using AAS.TwinEngine.DataEngine.ApplicationLogic.Extensions; using AAS.TwinEngine.DataEngine.ApplicationLogic.Services.AasRegistry; +using AAS.TwinEngine.DataEngine.ApplicationLogic.Services.AasRepository; +using AAS.TwinEngine.DataEngine.ApplicationLogic.Services.SubmodelRegistry; using AAS.TwinEngine.DataEngine.DomainModel.AasRegistry; +using AAS.TwinEngine.DataEngine.DomainModel.AasRepository; +using AAS.TwinEngine.DataEngine.DomainModel.Shared; +using AAS.TwinEngine.DataEngine.DomainModel.SubmodelRegistry; using AAS.TwinEngine.DataEngine.UnitTests.Api.Shared.MappingProfiles; +using AasCore.Aas3_1; + using Microsoft.Extensions.Logging; using NSubstitute; +using NSubstitute.ExceptionExtensions; namespace AAS.TwinEngine.DataEngine.UnitTests.Api.AasRegistry.Handler; public class ShellDescriptorHandlerTests { private readonly IShellDescriptorService _shellDescriptorService = Substitute.For(); + private readonly IAasRepositoryService _aasRepositoryService = Substitute.For(); + private readonly ISubmodelDescriptorService _submodelDescriptorService = Substitute.For(); private readonly ILogger _logger = Substitute.For>(); private readonly ShellDescriptorHandler _sut; - public ShellDescriptorHandlerTests() => _sut = new ShellDescriptorHandler(_logger, _shellDescriptorService); + public ShellDescriptorHandlerTests() => _sut = new ShellDescriptorHandler(_logger, _shellDescriptorService, _aasRepositoryService, _submodelDescriptorService); [Fact] public async Task GetAllShellDescriptors_ReturnsAllShellDescriptors_WhenExists() @@ -105,4 +116,94 @@ public async Task GetShellTemplateAsync_InvalidAasIdentifier_ThrowsInvalidUserIn var exception = await Assert.ThrowsAsync(() => _sut.GetShellDescriptorById(request, CancellationToken.None)); Assert.Equal("Invalid User Input.", exception.Message); } + + [Fact] + public async Task GetSubmodelDescriptorByAasId_ReturnsDescriptor_WhenSubmodelBelongsToAas() + { + const string AasId = "AasId"; + const string SubmodelId = "SubmodelId"; + var request = new GetSubmodelDescriptorByAasRequest(AasId.EncodeBase64Url(), SubmodelId.EncodeBase64Url()); + _aasRepositoryService.GetSubmodelRefByIdAsync(AasId, null, null, Arg.Any()) + .Returns(CreateSubmodelRefWithId(SubmodelId)); + _submodelDescriptorService.GetSubmodelDescriptorByIdAsync(SubmodelId, Arg.Any()) + .Returns(new SubmodelDescriptor { Id = SubmodelId }); + + var result = await _sut.GetSubmodelDescriptorByAasId(request, CancellationToken.None); + + Assert.IsType(result); + await _submodelDescriptorService.Received(1) + .GetSubmodelDescriptorByIdAsync(SubmodelId, Arg.Any()); + } + + [Fact] + public async Task GetSubmodelDescriptorByAasId_SubmodelNotInAas_ThrowsSubmodelDescriptorNotFoundException() + { + const string AasId = "AasId"; + const string SubmodelId = "SubmodelId"; + var request = new GetSubmodelDescriptorByAasRequest(AasId.EncodeBase64Url(), SubmodelId.EncodeBase64Url()); + _aasRepositoryService.GetSubmodelRefByIdAsync(AasId, null, null, Arg.Any()) + .Returns(CreateSubmodelRefWithId("OtherSubmodelId")); + + await Assert.ThrowsAsync( + () => _sut.GetSubmodelDescriptorByAasId(request, CancellationToken.None)); + } + + [Fact] + public async Task GetSubmodelDescriptorByAasId_AasNotFound_PropagatesException() + { + const string AasId = "AasId"; + const string SubmodelId = "SubmodelId"; + var request = new GetSubmodelDescriptorByAasRequest(AasId.EncodeBase64Url(), SubmodelId.EncodeBase64Url()); + _aasRepositoryService.GetSubmodelRefByIdAsync(AasId, null, null, Arg.Any()) + .ThrowsAsync(new TemplateNotFoundException()); + + await Assert.ThrowsAsync( + () => _sut.GetSubmodelDescriptorByAasId(request, CancellationToken.None)); + } + + [Fact] + public async Task GetAllSubmodelDescriptorsByAasId_ReturnsDescriptors_ForAllSubmodelsInAas() + { + const string AasId = "AasId"; + const string SubmodelId1 = "SubmodelId1"; + const string SubmodelId2 = "SubmodelId2"; + var request = new GetSubmodelDescriptorsByAasRequest(AasId.EncodeBase64Url(), null, null); + _aasRepositoryService.GetSubmodelRefByIdAsync(AasId, null, null, Arg.Any()) + .Returns(CreateSubmodelRefWithIds(SubmodelId1, SubmodelId2)); + _submodelDescriptorService.GetSubmodelDescriptorByIdAsync(SubmodelId1, Arg.Any()) + .Returns(new SubmodelDescriptor { Id = SubmodelId1 }); + _submodelDescriptorService.GetSubmodelDescriptorByIdAsync(SubmodelId2, Arg.Any()) + .Returns(new SubmodelDescriptor { Id = SubmodelId2 }); + + var result = await _sut.GetAllSubmodelDescriptorsByAasId(request, CancellationToken.None); + + Assert.NotNull(result); + Assert.Equal(2, result.Result?.Count); + } + + [Fact] + public async Task GetAllSubmodelDescriptorsByAasId_AasNotFound_PropagatesException() + { + const string AasId = "AasId"; + var request = new GetSubmodelDescriptorsByAasRequest(AasId.EncodeBase64Url(), null, null); + _aasRepositoryService.GetSubmodelRefByIdAsync(AasId, null, null, Arg.Any()) + .ThrowsAsync(new TemplateNotFoundException()); + + await Assert.ThrowsAsync( + () => _sut.GetAllSubmodelDescriptorsByAasId(request, CancellationToken.None)); + } + + private static SubmodelRef CreateSubmodelRefWithId(string submodelId) + => new() + { + PagingMetaData = new PagingMetaData(), + Result = [new Reference(ReferenceTypes.ModelReference, [new Key(KeyTypes.Submodel, submodelId)], null)] + }; + + private static SubmodelRef CreateSubmodelRefWithIds(params string[] submodelIds) + => new() + { + PagingMetaData = new PagingMetaData(), + Result = submodelIds.Select(id => (IReference)new Reference(ReferenceTypes.ModelReference, [new Key(KeyTypes.Submodel, id)], null)).ToList() + }; } diff --git a/source/AAS.TwinEngine.DataEngine.UnitTests/Api/AasRegistry/ShellDescriptorControllerTests.cs b/source/AAS.TwinEngine.DataEngine.UnitTests/Api/AasRegistry/ShellDescriptorControllerTests.cs index 14ca8dbd..40d282fb 100644 --- a/source/AAS.TwinEngine.DataEngine.UnitTests/Api/AasRegistry/ShellDescriptorControllerTests.cs +++ b/source/AAS.TwinEngine.DataEngine.UnitTests/Api/AasRegistry/ShellDescriptorControllerTests.cs @@ -3,6 +3,7 @@ using AAS.TwinEngine.DataEngine.Api.AasRegistry.Requests; using AAS.TwinEngine.DataEngine.Api.AasRegistry.Responses; using AAS.TwinEngine.DataEngine.Api.Shared; +using AAS.TwinEngine.DataEngine.Api.SubmodelRegistry.Responses; using AAS.TwinEngine.DataEngine.ApplicationLogic.Exceptions.Base; using AAS.TwinEngine.DataEngine.ApplicationLogic.Extensions; @@ -176,4 +177,41 @@ private static ShellDescriptorDto CreateShellDescriptor(string idShort, string i SubmodelDescriptors = null! }; } + + [Fact] + public async Task GetAllSubmodelDescriptorsByAasIdAsync_ReturnsOkResult() + { + const string AasId = "https://example.com/ids/aas/1170_1160_3052_6568"; + var encodedId = AasId.EncodeBase64Url(); + var expected = new SubmodelDescriptorsDto { PagingMetaData = new PagingMetaDataDto(), Result = [] }; + _handler.GetAllSubmodelDescriptorsByAasId(Arg.Any(), Arg.Any()) + .Returns(expected); + + var result = await _sut.GetAllSubmodelDescriptorsByAasIdAsync(encodedId, null, null, CancellationToken.None); + + var okResult = Assert.IsType(result.Result); + Assert.IsType(okResult.Value); + await _handler.Received(1).GetAllSubmodelDescriptorsByAasId( + Arg.Is(r => r.AasIdentifier == encodedId), + Arg.Any()); + } + + [Fact] + public async Task GetSubmodelDescriptorByAasIdAsync_ReturnsOkResult() + { + const string AasId = "https://example.com/ids/aas/1170_1160_3052_6568"; + const string SubmodelId = "https://example.com/submodel/1"; + var encodedAasId = AasId.EncodeBase64Url(); + var encodedSubmodelId = SubmodelId.EncodeBase64Url(); + _handler.GetSubmodelDescriptorByAasId(Arg.Any(), Arg.Any()) + .Returns(_expectedShellDescriptor.SubmodelDescriptors?.FirstOrDefault() ?? new SubmodelDescriptorDto()); + + var result = await _sut.GetSubmodelDescriptorByAasIdAsync(encodedAasId, encodedSubmodelId, CancellationToken.None); + + var okResult = Assert.IsType(result.Result); + Assert.NotNull(okResult.Value); + await _handler.Received(1).GetSubmodelDescriptorByAasId( + Arg.Is(r => r.AasIdentifier == encodedAasId && r.SubmodelIdentifier == encodedSubmodelId), + Arg.Any()); + } } diff --git a/source/AAS.TwinEngine.DataEngine.UnitTests/Api/AasRepository/AasRepositoryControllerTests.cs b/source/AAS.TwinEngine.DataEngine.UnitTests/Api/AasRepository/AasRepositoryControllerTests.cs index 07652bf1..54d5ce99 100644 --- a/source/AAS.TwinEngine.DataEngine.UnitTests/Api/AasRepository/AasRepositoryControllerTests.cs +++ b/source/AAS.TwinEngine.DataEngine.UnitTests/Api/AasRepository/AasRepositoryControllerTests.cs @@ -1,4 +1,4 @@ -using System.Text.Json; +using System.Text.Json; using System.Text.Json.Nodes; using System.Text.Json.Serialization; @@ -7,6 +7,8 @@ using AAS.TwinEngine.DataEngine.Api.AasRepository.Requests; using AAS.TwinEngine.DataEngine.Api.AasRepository.Responses; using AAS.TwinEngine.DataEngine.Api.Shared; +using AAS.TwinEngine.DataEngine.Api.SubmodelRepository.Requests; +using AAS.TwinEngine.DataEngine.Api.SubmodelRepository.Responses; using AAS.TwinEngine.DataEngine.ApplicationLogic.Extensions; using AasCore.Aas3_1; @@ -274,5 +276,63 @@ private static SubmodelRefDto CreateSubmodelRefDto() }; } + [Fact] + public async Task GetSubmodelByAasIdAsync_ReturnsOkWithJson() + { + var encodedAasId = AasIdentifier.EncodeBase64Url(); + const string SubmodelId = "SubmodelId"; + var encodedSubmodelId = SubmodelId.EncodeBase64Url(); + var submodel = new Submodel(SubmodelId); + _handler.GetSubmodelByAasIdAsync(Arg.Any(), Arg.Any()) + .Returns(submodel); + + var result = await _sut.GetSubmodelByAasIdAsync(encodedAasId, encodedSubmodelId, null, null, CancellationToken.None); + + var okResult = Assert.IsType(result.Result); + Assert.IsType(okResult.Value); + await _handler.Received(1).GetSubmodelByAasIdAsync( + Arg.Is(r => r.AasIdentifier == encodedAasId && r.SubmodelId == encodedSubmodelId), + Arg.Any()); + } + + [Fact] + public async Task GetAllSubmodelElementsByAasIdAsync_ReturnsOk() + { + var encodedAasId = AasIdentifier.EncodeBase64Url(); + const string SubmodelId = "SubmodelId"; + var encodedSubmodelId = SubmodelId.EncodeBase64Url(); + var expected = new SubmodelElementsDto { PagingMetaData = new PagingMetaDataDto(), Result = [] }; + _handler.GetAllSubmodelElementsByAasIdAsync(Arg.Any(), Arg.Any()) + .Returns(expected); + + var result = await _sut.GetAllSubmodelElementsByAasIdAsync(encodedAasId, encodedSubmodelId, null, null, null, null, CancellationToken.None); + + var okResult = Assert.IsType(result.Result); + Assert.IsType(okResult.Value); + await _handler.Received(1).GetAllSubmodelElementsByAasIdAsync( + Arg.Is(r => r.AasIdentifier == encodedAasId && r.SubmodelId == encodedSubmodelId), + Arg.Any()); + } + + [Fact] + public async Task GetSubmodelElementByAasIdAsync_ReturnsOkWithJson() + { + var encodedAasId = AasIdentifier.EncodeBase64Url(); + const string SubmodelId = "SubmodelId"; + var encodedSubmodelId = SubmodelId.EncodeBase64Url(); + const string IdShortPath = "ManufacturerName"; + var element = new Property(idShort: IdShortPath, valueType: DataTypeDefXsd.String); + _handler.GetSubmodelElementByAasIdAsync(Arg.Any(), Arg.Any()) + .Returns(element); + + var result = await _sut.GetSubmodelElementByAasIdAsync(encodedAasId, encodedSubmodelId, IdShortPath, CancellationToken.None); + + var okResult = Assert.IsType(result.Result); + Assert.IsType(okResult.Value); + await _handler.Received(1).GetSubmodelElementByAasIdAsync( + Arg.Is(r => r.AasIdentifier == encodedAasId && r.SubmodelId == encodedSubmodelId && r.IdShortPath == IdShortPath), + Arg.Any()); + } + } diff --git a/source/AAS.TwinEngine.DataEngine.UnitTests/Api/AasRepository/Handler/AasRepositoryHandlerTests.cs b/source/AAS.TwinEngine.DataEngine.UnitTests/Api/AasRepository/Handler/AasRepositoryHandlerTests.cs index d294c343..78d27c5f 100644 --- a/source/AAS.TwinEngine.DataEngine.UnitTests/Api/AasRepository/Handler/AasRepositoryHandlerTests.cs +++ b/source/AAS.TwinEngine.DataEngine.UnitTests/Api/AasRepository/Handler/AasRepositoryHandlerTests.cs @@ -1,28 +1,32 @@ -using AAS.TwinEngine.DataEngine.Api.AasRepository.Handler; +using AAS.TwinEngine.DataEngine.Api.AasRepository.Handler; using AAS.TwinEngine.DataEngine.Api.AasRepository.Requests; using AAS.TwinEngine.DataEngine.Api.AasRepository.Responses; using AAS.TwinEngine.DataEngine.Api.Shared; using AAS.TwinEngine.DataEngine.ApplicationLogic.Exceptions.Application; using AAS.TwinEngine.DataEngine.ApplicationLogic.Extensions; using AAS.TwinEngine.DataEngine.ApplicationLogic.Services.AasRepository; +using AAS.TwinEngine.DataEngine.ApplicationLogic.Services.SubmodelRepository; using AAS.TwinEngine.DataEngine.DomainModel.AasRepository; using AAS.TwinEngine.DataEngine.DomainModel.Shared; +using AAS.TwinEngine.DataEngine.DomainModel.SubmodelRepository; using AasCore.Aas3_1; using Microsoft.Extensions.Logging; using NSubstitute; +using NSubstitute.ExceptionExtensions; namespace AAS.TwinEngine.DataEngine.UnitTests.Api.AasRepository.Handler; public class AasRepositoryHandlerTests { private readonly IAasRepositoryService _aasRepositoryService = Substitute.For(); + private readonly ISubmodelRepositoryService _submodelRepositoryService = Substitute.For(); private readonly ILogger _logger = Substitute.For>(); private readonly AasRepositoryHandler _sut; - public AasRepositoryHandlerTests() => _sut = new AasRepositoryHandler(_logger, _aasRepositoryService); + public AasRepositoryHandlerTests() => _sut = new AasRepositoryHandler(_logger, _aasRepositoryService, _submodelRepositoryService); [Fact] public async Task GetShellsByAssetIdsAsync_WithNullAssetIds_ReturnsAllShells() @@ -262,5 +266,121 @@ private static AssetInformation CreateAssetInformation() defaultThumbnail: thumbnail ); } + + [Fact] + public async Task GetSubmodelByAasIdAsync_ReturnsSubmodel_WhenSubmodelBelongsToAas() + { + const string AasId = "AasId"; + const string SubmodelId = "SubmodelId"; + var request = new GetSubmodelByAasRequest(AasId.EncodeBase64Url(), SubmodelId.EncodeBase64Url()); + var expectedSubmodel = new Submodel(SubmodelId); + _aasRepositoryService.GetSubmodelRefByIdAsync(AasId, null, null, Arg.Any()) + .Returns(CreateSubmodelRefWithId(SubmodelId)); + _submodelRepositoryService.GetSubmodelByAasIdAsync(SubmodelId, null, Arg.Any()) + .Returns(expectedSubmodel); + + var result = await _sut.GetSubmodelByAasIdAsync(request, CancellationToken.None); + + Assert.IsType(result); + await _submodelRepositoryService.Received(1) + .GetSubmodelByAasIdAsync(SubmodelId, null, Arg.Any()); + } + + [Fact] + public async Task GetSubmodelByAasIdAsync_SubmodelNotInAas_ThrowsSubmodelNotFoundException() + { + const string AasId = "AasId"; + const string SubmodelId = "SubmodelId"; + var request = new GetSubmodelByAasRequest(AasId.EncodeBase64Url(), SubmodelId.EncodeBase64Url()); + _aasRepositoryService.GetSubmodelRefByIdAsync(AasId, null, null, Arg.Any()) + .Returns(CreateSubmodelRefWithId("OtherSubmodelId")); + + await Assert.ThrowsAsync( + () => _sut.GetSubmodelByAasIdAsync(request, CancellationToken.None)); + } + + [Fact] + public async Task GetSubmodelByAasIdAsync_AasNotFound_PropagatesException() + { + const string AasId = "AasId"; + const string SubmodelId = "SubmodelId"; + var request = new GetSubmodelByAasRequest(AasId.EncodeBase64Url(), SubmodelId.EncodeBase64Url()); + _aasRepositoryService.GetSubmodelRefByIdAsync(AasId, null, null, Arg.Any()) + .ThrowsAsync(new TemplateNotFoundException()); + + await Assert.ThrowsAsync( + () => _sut.GetSubmodelByAasIdAsync(request, CancellationToken.None)); + } + + [Fact] + public async Task GetAllSubmodelElementsByAasIdAsync_ReturnsList_WhenSubmodelBelongsToAas() + { + const string AasId = "AasId"; + const string SubmodelId = "SubmodelId"; + var request = new GetAllSubmodelElementsByAasRequest(AasId.EncodeBase64Url(), SubmodelId.EncodeBase64Url(), null, null); + _aasRepositoryService.GetSubmodelRefByIdAsync(AasId, null, null, Arg.Any()) + .Returns(CreateSubmodelRefWithId(SubmodelId)); + _submodelRepositoryService.GetAllSubmodelElementsAsync(SubmodelId, null, null, null, Arg.Any()) + .Returns(new SubmodelElementsPage { PagingMetaData = new PagingMetaData(), Result = [] }); + + var result = await _sut.GetAllSubmodelElementsByAasIdAsync(request, CancellationToken.None); + + Assert.NotNull(result); + await _submodelRepositoryService.Received(1) + .GetAllSubmodelElementsAsync(SubmodelId, null, null, null, Arg.Any()); + } + + [Fact] + public async Task GetAllSubmodelElementsByAasIdAsync_SubmodelNotInAas_ThrowsSubmodelNotFoundException() + { + const string AasId = "AasId"; + const string SubmodelId = "SubmodelId"; + var request = new GetAllSubmodelElementsByAasRequest(AasId.EncodeBase64Url(), SubmodelId.EncodeBase64Url(), null, null); + _aasRepositoryService.GetSubmodelRefByIdAsync(AasId, null, null, Arg.Any()) + .Returns(CreateSubmodelRefWithId("OtherSubmodelId")); + + await Assert.ThrowsAsync( + () => _sut.GetAllSubmodelElementsByAasIdAsync(request, CancellationToken.None)); + } + + [Fact] + public async Task GetSubmodelElementByAasIdAsync_ReturnsElement_WhenSubmodelBelongsToAas() + { + const string AasId = "AasId"; + const string SubmodelId = "SubmodelId"; + const string IdShortPath = "ManufacturerName"; + var request = new GetSubmodelElementByAasRequest(AasId.EncodeBase64Url(), SubmodelId.EncodeBase64Url(), IdShortPath); + var expectedElement = new Property(idShort: IdShortPath, valueType: DataTypeDefXsd.String); + _aasRepositoryService.GetSubmodelRefByIdAsync(AasId, null, null, Arg.Any()) + .Returns(CreateSubmodelRefWithId(SubmodelId)); + _submodelRepositoryService.GetSubmodelElementAsync(SubmodelId, IdShortPath, Arg.Any()) + .Returns(expectedElement); + + var result = await _sut.GetSubmodelElementByAasIdAsync(request, CancellationToken.None); + + Assert.IsType(result); + await _submodelRepositoryService.Received(1) + .GetSubmodelElementAsync(SubmodelId, IdShortPath, Arg.Any()); + } + + [Fact] + public async Task GetSubmodelElementByAasIdAsync_SubmodelNotInAas_ThrowsSubmodelNotFoundException() + { + const string AasId = "AasId"; + const string SubmodelId = "SubmodelId"; + var request = new GetSubmodelElementByAasRequest(AasId.EncodeBase64Url(), SubmodelId.EncodeBase64Url(), "SomePath"); + _aasRepositoryService.GetSubmodelRefByIdAsync(AasId, null, null, Arg.Any()) + .Returns(CreateSubmodelRefWithId("OtherSubmodelId")); + + await Assert.ThrowsAsync( + () => _sut.GetSubmodelElementByAasIdAsync(request, CancellationToken.None)); + } + + private static SubmodelRef CreateSubmodelRefWithId(string submodelId) + => new() + { + PagingMetaData = new PagingMetaData(), + Result = [new Reference(ReferenceTypes.ModelReference, [new Key(KeyTypes.Submodel, submodelId)], null)] + }; } diff --git a/source/AAS.TwinEngine.DataEngine/Api/AasRegistry/Handler/IShellDescriptorHandler.cs b/source/AAS.TwinEngine.DataEngine/Api/AasRegistry/Handler/IShellDescriptorHandler.cs index 30a09d53..3f4ebcb5 100644 --- a/source/AAS.TwinEngine.DataEngine/Api/AasRegistry/Handler/IShellDescriptorHandler.cs +++ b/source/AAS.TwinEngine.DataEngine/Api/AasRegistry/Handler/IShellDescriptorHandler.cs @@ -1,5 +1,6 @@ using AAS.TwinEngine.DataEngine.Api.AasRegistry.Requests; using AAS.TwinEngine.DataEngine.Api.AasRegistry.Responses; +using AAS.TwinEngine.DataEngine.Api.SubmodelRegistry.Responses; namespace AAS.TwinEngine.DataEngine.Api.AasRegistry.Handler; @@ -8,4 +9,8 @@ public interface IShellDescriptorHandler Task GetAllShellDescriptors(GetShellDescriptorsRequest request, CancellationToken cancellationToken); Task GetShellDescriptorById(GetShellDescriptorRequest request, CancellationToken cancellationToken); + + Task GetAllSubmodelDescriptorsByAasId(GetSubmodelDescriptorsByAasRequest request, CancellationToken cancellationToken); + + Task GetSubmodelDescriptorByAasId(GetSubmodelDescriptorByAasRequest request, CancellationToken cancellationToken); } diff --git a/source/AAS.TwinEngine.DataEngine/Api/AasRegistry/Handler/ShellDescriptorHandler.cs b/source/AAS.TwinEngine.DataEngine/Api/AasRegistry/Handler/ShellDescriptorHandler.cs index b3eb23fc..eed22e06 100644 --- a/source/AAS.TwinEngine.DataEngine/Api/AasRegistry/Handler/ShellDescriptorHandler.cs +++ b/source/AAS.TwinEngine.DataEngine/Api/AasRegistry/Handler/ShellDescriptorHandler.cs @@ -1,22 +1,28 @@ using AAS.TwinEngine.DataEngine.Api.AasRegistry.MappingProfiles; using AAS.TwinEngine.DataEngine.Api.AasRegistry.Requests; using AAS.TwinEngine.DataEngine.Api.AasRegistry.Responses; +using AAS.TwinEngine.DataEngine.Api.SubmodelRegistry.MappingProfiles; +using AAS.TwinEngine.DataEngine.Api.SubmodelRegistry.Responses; using AAS.TwinEngine.DataEngine.ApplicationLogic.Exceptions.Application; using AAS.TwinEngine.DataEngine.ApplicationLogic.Extensions; using AAS.TwinEngine.DataEngine.ApplicationLogic.Services.AasRegistry; +using AAS.TwinEngine.DataEngine.ApplicationLogic.Services.AasRepository; +using AAS.TwinEngine.DataEngine.ApplicationLogic.Services.SubmodelRegistry; namespace AAS.TwinEngine.DataEngine.Api.AasRegistry.Handler; public class ShellDescriptorHandler( ILogger logger, - IShellDescriptorService shellDescriptorService) : IShellDescriptorHandler + IShellDescriptorService shellDescriptorService, + IAasRepositoryService aasRepositoryService, + ISubmodelDescriptorService submodelDescriptorService) : IShellDescriptorHandler { public Task GetAllShellDescriptors(GetShellDescriptorsRequest request, CancellationToken cancellationToken) { request?.Limit.ValidateLimit(logger); request?.Cursor?.ValidateCursor(logger); - return GetShellDescriptorResourceAsync( + return GetResourceAsync( null, "shell descriptors", _ => shellDescriptorService.GetAllShellDescriptorsAsync(request.Limit, request.Cursor, cancellationToken), @@ -25,14 +31,56 @@ public Task GetAllShellDescriptors(GetShellDescriptorsReque } public Task GetShellDescriptorById(GetShellDescriptorRequest request, CancellationToken cancellationToken) - => GetShellDescriptorResourceAsync( + => GetResourceAsync( request?.AasIdentifier, "shell descriptor", id => shellDescriptorService.GetShellDescriptorByIdAsync(id!, cancellationToken), descriptor => descriptor.ToDto() ); - private async Task GetShellDescriptorResourceAsync( + public Task GetAllSubmodelDescriptorsByAasId(GetSubmodelDescriptorsByAasRequest request, CancellationToken cancellationToken) + { + request?.Limit.ValidateLimit(logger); + request?.Cursor?.ValidateCursor(logger); + + return GetResourceAsync( + request?.AasIdentifier, + "submodel descriptors by AasId", + aasId => shellDescriptorService.GetAllSubmodelDescriptorsByAasIdAsync( + aasId!, + request.Limit, + request.Cursor, + cancellationToken), + descriptors => descriptors.ToDto()); + } + + public Task GetSubmodelDescriptorByAasId(GetSubmodelDescriptorByAasRequest request, CancellationToken cancellationToken) + { + return GetResourceAsync( + request?.AasIdentifier, + request?.SubmodelIdentifier, + "submodel descriptor", + (aasId, submodelId) => shellDescriptorService.GetSubmodelDescriptorByAasIdAsync(aasId, submodelId, cancellationToken), + descriptor => descriptor.ToDto()); + } + + private async Task GetResourceAsync( + string? encodedAasId, + string? encodedSubmodelId, + string resourceName, + Func> fetchFunc, + Func mapFunc) + { + var decodedAasId = encodedAasId?.DecodeBase64Url(logger); + var decodedSubmodelId = encodedSubmodelId?.DecodeBase64Url(logger); + logger.LogInformation("Get {ResourceName} for AAS: {AasId}, Submodel: {SubmodelId}", resourceName, decodedAasId, decodedSubmodelId); + + var result = await fetchFunc(decodedAasId!, decodedSubmodelId!).ConfigureAwait(false); + ValidateResourceExists(result, resourceName); + return mapFunc(result!); + } + + private async Task GetResourceAsync( string? encodedId, string resourceName, Func> serviceFetchFunc, @@ -49,13 +97,13 @@ private async Task GetShellDescriptorResourceAsync( private void LogRequestStart(string resourceName, string? decodedId) { - if (resourceName is "shell descriptor") + if (resourceName is not "shell descriptor") { - logger.LogInformation("Start executing get request for {ResourceName}", resourceName); + logger.LogInformation("Start executing get request for {ResourceName} for AAS Identifier: {AasIdentifier}", resourceName, decodedId); } else { - logger.LogInformation("Start executing get request for {ResourceName} for AAS Identifier: {AasIdentifier}", resourceName, decodedId); + logger.LogInformation("Start executing get request for {ResourceName}", resourceName); } } diff --git a/source/AAS.TwinEngine.DataEngine/Api/AasRegistry/Requests/GetSubmodelDescriptorByAasRequest.cs b/source/AAS.TwinEngine.DataEngine/Api/AasRegistry/Requests/GetSubmodelDescriptorByAasRequest.cs new file mode 100644 index 00000000..34178ee3 --- /dev/null +++ b/source/AAS.TwinEngine.DataEngine/Api/AasRegistry/Requests/GetSubmodelDescriptorByAasRequest.cs @@ -0,0 +1,3 @@ +namespace AAS.TwinEngine.DataEngine.Api.AasRegistry.Requests; + +public record GetSubmodelDescriptorByAasRequest(string AasIdentifier, string SubmodelIdentifier); diff --git a/source/AAS.TwinEngine.DataEngine/Api/AasRegistry/Requests/GetSubmodelDescriptorsByAasRequest.cs b/source/AAS.TwinEngine.DataEngine/Api/AasRegistry/Requests/GetSubmodelDescriptorsByAasRequest.cs new file mode 100644 index 00000000..06c1a7cf --- /dev/null +++ b/source/AAS.TwinEngine.DataEngine/Api/AasRegistry/Requests/GetSubmodelDescriptorsByAasRequest.cs @@ -0,0 +1,3 @@ +namespace AAS.TwinEngine.DataEngine.Api.AasRegistry.Requests; + +public record GetSubmodelDescriptorsByAasRequest(string AasIdentifier, int? Limit, string? Cursor); diff --git a/source/AAS.TwinEngine.DataEngine/Api/AasRegistry/ShellDescriptorController.cs b/source/AAS.TwinEngine.DataEngine/Api/AasRegistry/ShellDescriptorController.cs index 07b34467..f49557e8 100644 --- a/source/AAS.TwinEngine.DataEngine/Api/AasRegistry/ShellDescriptorController.cs +++ b/source/AAS.TwinEngine.DataEngine/Api/AasRegistry/ShellDescriptorController.cs @@ -4,6 +4,7 @@ using AAS.TwinEngine.DataEngine.Api.AasRegistry.Handler; using AAS.TwinEngine.DataEngine.Api.AasRegistry.Requests; using AAS.TwinEngine.DataEngine.Api.AasRegistry.Responses; +using AAS.TwinEngine.DataEngine.Api.SubmodelRegistry.Responses; using AAS.TwinEngine.DataEngine.ApplicationLogic.Exceptions.Responses; using Asp.Versioning; @@ -67,4 +68,58 @@ public async Task> GetShellDescriptorByIdAsync( var response = await shellDescriptorHandler.GetShellDescriptorById(request, cancellationToken).ConfigureAwait(false); return Ok(response); } + + /// + /// Returns all Submodel Descriptors + /// + /// The Asset Administration Shell's unique id (UTF8-BASE64-URL-encoded) + /// The maximum number of elements in the response array + /// A server-generated identifier retrieved from pagingMetadata that specifies from which position the result listing should continue + /// Cancellation token + /// Requested Submodel Descriptors + /// Bad Request, e.g. the request parameters of the format of the request body is wrong. + /// Not Found + /// Internal Server Error + [HttpGet("{aasIdentifier}/submodel-descriptors")] + [ProducesResponseType(typeof(SubmodelDescriptorsDto), (int)HttpStatusCode.OK)] + [ProducesResponseType(typeof(ServiceErrorResponse), (int)HttpStatusCode.BadRequest)] + [ProducesResponseType(typeof(ServiceErrorResponse), (int)HttpStatusCode.NotFound)] + [ProducesResponseType(typeof(ServiceErrorResponse), (int)HttpStatusCode.InternalServerError)] + public async Task> GetAllSubmodelDescriptorsByAasIdAsync( + [FromRoute] string aasIdentifier, + [FromQuery] int? limit, + [FromQuery] string? cursor, + CancellationToken cancellationToken) + { + logger.LogInformation("Get All Submodel Descriptors through superpath for AAS"); + var request = new GetSubmodelDescriptorsByAasRequest(aasIdentifier, limit, cursor); + var response = await shellDescriptorHandler.GetAllSubmodelDescriptorsByAasId(request, cancellationToken).ConfigureAwait(false); + return Ok(response); + } + + /// + /// Returns a specific Submodel Descriptor + /// + /// The Asset Administration Shell's unique id (UTF8-BASE64-URL-encoded) + /// The Submodel's unique id (UTF8-BASE64-URL-encoded) + /// Cancellation token + /// Requested Submodel Descriptor + /// Bad Request, e.g. the request parameters of the format of the request body is wrong. + /// Not Found + /// Internal Server Error + [HttpGet("{aasIdentifier}/submodel-descriptors/{submodelIdentifier}")] + [ProducesResponseType(typeof(SubmodelDescriptorDto), (int)HttpStatusCode.OK)] + [ProducesResponseType(typeof(ServiceErrorResponse), (int)HttpStatusCode.BadRequest)] + [ProducesResponseType(typeof(ServiceErrorResponse), (int)HttpStatusCode.NotFound)] + [ProducesResponseType(typeof(ServiceErrorResponse), (int)HttpStatusCode.InternalServerError)] + public async Task> GetSubmodelDescriptorByAasIdAsync( + [FromRoute] string aasIdentifier, + [FromRoute] string submodelIdentifier, + CancellationToken cancellationToken) + { + logger.LogInformation("Get Submodel Descriptor through superpath for AAS"); + var request = new GetSubmodelDescriptorByAasRequest(aasIdentifier, submodelIdentifier); + var response = await shellDescriptorHandler.GetSubmodelDescriptorByAasId(request, cancellationToken).ConfigureAwait(false); + return Ok(response); + } } diff --git a/source/AAS.TwinEngine.DataEngine/Api/AasRepository/AasRepositoryController.cs b/source/AAS.TwinEngine.DataEngine/Api/AasRepository/AasRepositoryController.cs index 4abacbd3..cfd95a7b 100644 --- a/source/AAS.TwinEngine.DataEngine/Api/AasRepository/AasRepositoryController.cs +++ b/source/AAS.TwinEngine.DataEngine/Api/AasRepository/AasRepositoryController.cs @@ -5,6 +5,8 @@ using AAS.TwinEngine.DataEngine.Api.AasRepository.Handler; using AAS.TwinEngine.DataEngine.Api.AasRepository.Requests; using AAS.TwinEngine.DataEngine.Api.AasRepository.Responses; +using AAS.TwinEngine.DataEngine.Api.SubmodelRepository.Requests; +using AAS.TwinEngine.DataEngine.Api.SubmodelRepository.Responses; using AAS.TwinEngine.DataEngine.ApplicationLogic.Exceptions.Responses; using AasCore.Aas3_1; @@ -119,4 +121,100 @@ public async Task> GetSubmodelRefByIdAsync([FromRoute] var response = await aasRepositoryHandler.GetSubmodelRefByIdAsync(request, cancellationToken).ConfigureAwait(false); return Ok(response); } + + /// + /// Returns the Submodel + /// + /// The Asset Administration Shell’s unique id (UTF8-BASE64-URL-encoded) + /// The Submodel’s unique id (UTF8-BASE64-URL-encoded) + /// Determines the structural depth of the respective resource content. + /// Determines to which extent the resource is being serialized. + /// Cancellation token. + /// Requested Submodel + /// Bad Request, e.g. the request parameters of the format of the request body is wrong. + /// Not Found + /// Internal Server Error + [HttpGet("{aasIdentifier}/submodels/{submodelIdentifier}")] + [ProducesResponseType(typeof(Submodel), (int)HttpStatusCode.OK)] + [ProducesResponseType(typeof(ServiceErrorResponse), (int)HttpStatusCode.BadRequest)] + [ProducesResponseType(typeof(ServiceErrorResponse), (int)HttpStatusCode.NotFound)] + [ProducesResponseType(typeof(ServiceErrorResponse), (int)HttpStatusCode.InternalServerError)] + public async Task> GetSubmodelByAasIdAsync( + [FromRoute] string aasIdentifier, + [FromRoute] string submodelIdentifier, + CancellationToken cancellationToken, + [FromQuery] Level level = Level.deep, + [FromQuery] Extent extent = Extent.withoutBlobValue) + { + logger.LogInformation("Get Submodel through superpath for AAS"); + var request = new GetSubmodelByAasRequest(aasIdentifier, submodelIdentifier, level, extent); + var response = await aasRepositoryHandler.GetSubmodelByAasIdAsync(request, cancellationToken).ConfigureAwait(false); + return Ok(Jsonization.Serialize.ToJsonObject(response)); + } + + /// + /// Returns all submodel elements including their hierarchy + /// + /// The Asset Administration Shell's unique id (UTF8-BASE64-URL-encoded) + /// The Submodel's unique id (UTF8-BASE64-URL-encoded) + /// The maximum number of elements in the response array + /// A server-generated identifier retrieved from pagingMetadata that specifies from which position the result listing should continue + /// Determines the structural depth of the respective resource content. + /// Determines to which extent the resource is being serialized. + /// Cancellation token. + /// List of found submodel elements + /// Bad Request, e.g. the request parameters of the format of the request body is wrong. + /// Not Found + /// Internal Server Error + [HttpGet("{aasIdentifier}/submodels/{submodelIdentifier}/submodel-elements")] + [ProducesResponseType(typeof(SubmodelElementsDto), StatusCodes.Status200OK)] + [ProducesResponseType(typeof(ServiceErrorResponse), StatusCodes.Status400BadRequest)] + [ProducesResponseType(typeof(ServiceErrorResponse), StatusCodes.Status404NotFound)] + [ProducesResponseType(typeof(ServiceErrorResponse), StatusCodes.Status500InternalServerError)] + public async Task> GetAllSubmodelElementsByAasIdAsync( + [FromRoute] string aasIdentifier, + [FromRoute] string submodelIdentifier, + [FromQuery] int? limit, + [FromQuery] string? cursor, + CancellationToken cancellationToken, + [FromQuery] Level level = Level.deep, + [FromQuery] Extent extent = Extent.withoutBlobValue) + { + logger.LogInformation("Get All Submodel Elements through superpath for AAS"); + var request = new GetAllSubmodelElementsByAasRequest(aasIdentifier, submodelIdentifier, limit, cursor, level, extent); + var response = await aasRepositoryHandler.GetAllSubmodelElementsByAasIdAsync(request, cancellationToken).ConfigureAwait(false); + return Ok(response); + } + + /// + /// Returns a specific submodel element from the Submodel at a specified path + /// + /// The Asset Administration Shell's unique id (UTF8-BASE64-URL-encoded) + /// The Submodel's unique id (UTF8-BASE64-URL-encoded) + /// IdShort path to the submodel element (dot-notation) + /// Determines the structural depth of the respective resource content. + /// Determines to which extent the resource is being serialized. + /// Cancellation token. + /// Requested submodel element + /// Bad Request, e.g. the request parameters of the format of the request body is wrong. + /// Not Found + /// Internal Server Error + [HttpGet("{aasIdentifier}/submodels/{submodelIdentifier}/submodel-elements/{idShortPath}")] + [ProducesResponseType(typeof(ISubmodelElement), StatusCodes.Status200OK)] + [ProducesResponseType(typeof(ServiceErrorResponse), StatusCodes.Status400BadRequest)] + [ProducesResponseType(typeof(ServiceErrorResponse), StatusCodes.Status404NotFound)] + [ProducesResponseType(typeof(ServiceErrorResponse), StatusCodes.Status500InternalServerError)] + public async Task> GetSubmodelElementByAasIdAsync( + [FromRoute] string aasIdentifier, + [FromRoute] string submodelIdentifier, + [FromRoute] string idShortPath, + CancellationToken cancellationToken, + [FromQuery] Level level = Level.deep, + [FromQuery] Extent extent = Extent.withoutBlobValue) + { + logger.LogInformation("Get Submodel Element through superpath for AAS"); + var request = new GetSubmodelElementByAasRequest(aasIdentifier, submodelIdentifier, idShortPath, level, extent); + var response = await aasRepositoryHandler.GetSubmodelElementByAasIdAsync(request, cancellationToken).ConfigureAwait(false); + return Ok(Jsonization.Serialize.ToJsonObject(response)); + } } diff --git a/source/AAS.TwinEngine.DataEngine/Api/AasRepository/Handler/AasRepositoryHandler.cs b/source/AAS.TwinEngine.DataEngine/Api/AasRepository/Handler/AasRepositoryHandler.cs index a617b659..ef6b7abf 100644 --- a/source/AAS.TwinEngine.DataEngine/Api/AasRepository/Handler/AasRepositoryHandler.cs +++ b/source/AAS.TwinEngine.DataEngine/Api/AasRepository/Handler/AasRepositoryHandler.cs @@ -1,13 +1,16 @@ -using System.Text.Json; +using System.Text.Json; using AAS.TwinEngine.DataEngine.Api.AasRepository.MappingProfiles; using AAS.TwinEngine.DataEngine.Api.AasRepository.Requests; using AAS.TwinEngine.DataEngine.Api.AasRepository.Responses; using AAS.TwinEngine.DataEngine.Api.Shared; +using AAS.TwinEngine.DataEngine.Api.SubmodelRepository.MappingProfiles; +using AAS.TwinEngine.DataEngine.Api.SubmodelRepository.Responses; using AAS.TwinEngine.DataEngine.ApplicationLogic.Exceptions.Application; using AAS.TwinEngine.DataEngine.ApplicationLogic.Extensions; using AAS.TwinEngine.DataEngine.ApplicationLogic.Services.AasRepository; using AAS.TwinEngine.DataEngine.DomainModel.AasRepository; +using AAS.TwinEngine.DataEngine.DomainModel.SubmodelRepository; using AasCore.Aas3_1; @@ -67,6 +70,44 @@ public Task GetSubmodelRefByIdAsync(GetSubmodelRefRequest request, ); } + public Task GetSubmodelByAasIdAsync(GetSubmodelByAasRequest request, CancellationToken cancellationToken) + { + return GetResourceByIdAsync( + request?.AasIdentifier, + request?.SubmodelId, + "submodel By AasID", + (aasId, submodelId) => aasRepositoryService.GetSubmodelByAasIdAsync(aasId, submodelId, request.Level, request.Extent, cancellationToken)); + } + + public Task GetAllSubmodelElementsByAasIdAsync( + GetAllSubmodelElementsByAasRequest request, + CancellationToken cancellationToken) + { + request?.Limit.ValidateLimit(logger); + request?.Cursor?.ValidateCursor(logger); + + return GetResourceByIdAsync( + request?.AasIdentifier, + request?.SubmodelId, + "submodel elements By AasID", + async (aasId, submodelId) => (await aasRepositoryService + .GetAllSubmodelElementsByAasIdAsync(aasId, submodelId, request.Level, request.Extent, request?.Limit, request?.Cursor, cancellationToken) + .ConfigureAwait(false)).ToDto()); + } + + public Task GetSubmodelElementByAasIdAsync( + GetSubmodelElementByAasRequest request, + CancellationToken cancellationToken) + { + request?.IdShortPath.ValidateIdShortPath(nameof(request.IdShortPath), logger); + + return GetResourceByIdAsync( + request?.AasIdentifier, + request?.SubmodelId, + "submodel element By AasID", + (aasId, submodelId) => aasRepositoryService.GetSubmodelElementByAasIdAsync(aasId, submodelId, request?.IdShortPath, cancellationToken)); + } + private Task GetResourceByIdAsync( string? encodedId, string resourceName, @@ -88,6 +129,25 @@ private async Task GetResourceByIdAsync( return mapFunc(result!); } + private async Task GetResourceByIdAsync( + string? encodedAasId, + string? encodedSubmodelId, + string resourceName, + Func> fetchFunc) + { + var decodedAasId = encodedAasId?.DecodeBase64Url(logger); + var decodedSubmodelId = encodedSubmodelId?.DecodeBase64Url(logger); + + logger.LogInformation("Start executing get request for {ResourceName}. AAS: {AasId}, Submodel: {SubmodelId}", resourceName, decodedAasId, decodedSubmodelId); + + var result = await fetchFunc(decodedAasId!, decodedSubmodelId!) + .ConfigureAwait(false); + + ValidateResourceExists(result, resourceName, decodedSubmodelId!); + + return result!; + } + private void ValidateResourceExists(T? result, string resourceName, string decodedId) { if (result is null) diff --git a/source/AAS.TwinEngine.DataEngine/Api/AasRepository/Handler/IAasRepositoryHandler.cs b/source/AAS.TwinEngine.DataEngine/Api/AasRepository/Handler/IAasRepositoryHandler.cs index 7921204d..13c841d1 100644 --- a/source/AAS.TwinEngine.DataEngine/Api/AasRepository/Handler/IAasRepositoryHandler.cs +++ b/source/AAS.TwinEngine.DataEngine/Api/AasRepository/Handler/IAasRepositoryHandler.cs @@ -2,6 +2,7 @@ using AAS.TwinEngine.DataEngine.Api.AasRepository.Requests; using AAS.TwinEngine.DataEngine.Api.AasRepository.Responses; +using AAS.TwinEngine.DataEngine.Api.SubmodelRepository.Responses; using AasCore.Aas3_1; @@ -16,4 +17,10 @@ public interface IAasRepositoryHandler Task GetAssetInformationByIdAsync(GetAssetInformationRequest request, CancellationToken cancellationToken); Task GetSubmodelRefByIdAsync(GetSubmodelRefRequest request, CancellationToken cancellationToken); + + Task GetSubmodelByAasIdAsync(GetSubmodelByAasRequest request, CancellationToken cancellationToken); + + Task GetAllSubmodelElementsByAasIdAsync(GetAllSubmodelElementsByAasRequest request, CancellationToken cancellationToken); + + Task GetSubmodelElementByAasIdAsync(GetSubmodelElementByAasRequest request, CancellationToken cancellationToken); } diff --git a/source/AAS.TwinEngine.DataEngine/Api/AasRepository/Requests/GetAllSubmodelElementsByAasRequest.cs b/source/AAS.TwinEngine.DataEngine/Api/AasRepository/Requests/GetAllSubmodelElementsByAasRequest.cs new file mode 100644 index 00000000..26726822 --- /dev/null +++ b/source/AAS.TwinEngine.DataEngine/Api/AasRepository/Requests/GetAllSubmodelElementsByAasRequest.cs @@ -0,0 +1,11 @@ +using AAS.TwinEngine.DataEngine.Api.SubmodelRepository.Requests; + +namespace AAS.TwinEngine.DataEngine.Api.AasRepository.Requests; + +public record GetAllSubmodelElementsByAasRequest( + string AasIdentifier, + string SubmodelId, + int? Limit, + string? Cursor, + Level Level, + Extent Extent); diff --git a/source/AAS.TwinEngine.DataEngine/Api/AasRepository/Requests/GetSubmodelByAasRequest.cs b/source/AAS.TwinEngine.DataEngine/Api/AasRepository/Requests/GetSubmodelByAasRequest.cs new file mode 100644 index 00000000..9d3f284b --- /dev/null +++ b/source/AAS.TwinEngine.DataEngine/Api/AasRepository/Requests/GetSubmodelByAasRequest.cs @@ -0,0 +1,5 @@ +using AAS.TwinEngine.DataEngine.Api.SubmodelRepository.Requests; + +namespace AAS.TwinEngine.DataEngine.Api.AasRepository.Requests; + +public record GetSubmodelByAasRequest(string AasIdentifier, string SubmodelId, Level Level, Extent Extent); diff --git a/source/AAS.TwinEngine.DataEngine/Api/AasRepository/Requests/GetSubmodelElementByAasRequest.cs b/source/AAS.TwinEngine.DataEngine/Api/AasRepository/Requests/GetSubmodelElementByAasRequest.cs new file mode 100644 index 00000000..ae1796f1 --- /dev/null +++ b/source/AAS.TwinEngine.DataEngine/Api/AasRepository/Requests/GetSubmodelElementByAasRequest.cs @@ -0,0 +1,5 @@ +using AAS.TwinEngine.DataEngine.Api.SubmodelRepository.Requests; + +namespace AAS.TwinEngine.DataEngine.Api.AasRepository.Requests; + +public record GetSubmodelElementByAasRequest(string AasIdentifier, string SubmodelId, string IdShortPath, Level level, Extent extent); diff --git a/source/AAS.TwinEngine.DataEngine/ApplicationLogic/Services/AasRegistry/IShellDescriptorService.cs b/source/AAS.TwinEngine.DataEngine/ApplicationLogic/Services/AasRegistry/IShellDescriptorService.cs index 143996de..21a68c1d 100644 --- a/source/AAS.TwinEngine.DataEngine/ApplicationLogic/Services/AasRegistry/IShellDescriptorService.cs +++ b/source/AAS.TwinEngine.DataEngine/ApplicationLogic/Services/AasRegistry/IShellDescriptorService.cs @@ -1,4 +1,5 @@ using AAS.TwinEngine.DataEngine.DomainModel.AasRegistry; +using AAS.TwinEngine.DataEngine.DomainModel.SubmodelRegistry; namespace AAS.TwinEngine.DataEngine.ApplicationLogic.Services.AasRegistry; @@ -7,4 +8,8 @@ public interface IShellDescriptorService Task GetAllShellDescriptorsAsync(int? limit, string? cursor, CancellationToken cancellationToken); Task GetShellDescriptorByIdAsync(string id, CancellationToken cancellationToken); + + Task GetAllSubmodelDescriptorsByAasIdAsync(string aasId, int? limit, string? cursor, CancellationToken cancellationToken); + + Task GetSubmodelDescriptorByAasIdAsync(string aasId, string submodelId, CancellationToken cancellationToken); } diff --git a/source/AAS.TwinEngine.DataEngine/ApplicationLogic/Services/AasRegistry/ShellDescriptorService.cs b/source/AAS.TwinEngine.DataEngine/ApplicationLogic/Services/AasRegistry/ShellDescriptorService.cs index fc5d15c3..b2bd9258 100644 --- a/source/AAS.TwinEngine.DataEngine/ApplicationLogic/Services/AasRegistry/ShellDescriptorService.cs +++ b/source/AAS.TwinEngine.DataEngine/ApplicationLogic/Services/AasRegistry/ShellDescriptorService.cs @@ -1,8 +1,12 @@ using AAS.TwinEngine.DataEngine.ApplicationLogic.Exceptions.Application; using AAS.TwinEngine.DataEngine.ApplicationLogic.Exceptions.Infrastructure; +using AAS.TwinEngine.DataEngine.ApplicationLogic.Extensions; using AAS.TwinEngine.DataEngine.ApplicationLogic.Services.AasEnvironment.Providers; +using AAS.TwinEngine.DataEngine.ApplicationLogic.Services.AasRepository; using AAS.TwinEngine.DataEngine.ApplicationLogic.Services.Plugin; +using AAS.TwinEngine.DataEngine.ApplicationLogic.Services.SubmodelRegistry; using AAS.TwinEngine.DataEngine.DomainModel.AasRegistry; +using AAS.TwinEngine.DataEngine.DomainModel.SubmodelRegistry; using AAS.TwinEngine.DataEngine.ServiceConfiguration.Config; using Microsoft.Extensions.Options; @@ -18,7 +22,9 @@ public class ShellDescriptorService( IPluginDataHandler pluginDataHandler, IPluginManifestConflictHandler pluginManifestConflictHandler, ILogger logger, - IOptions templateManagementConfig) : IShellDescriptorService + IOptions templateManagementConfig, + ISubmodelDescriptorService submodelDescriptorService, + IAasRepositoryService aasRepositoryService) : IShellDescriptorService { private readonly int _concurrentOperationsLimit = templateManagementConfig.Value.AasTemplateRegistry.ConcurrentOperationsLimit; public async Task GetAllShellDescriptorsAsync(int? limit, string? cursor, CancellationToken cancellationToken) @@ -94,6 +100,82 @@ public class ShellDescriptorService( throw new InternalDataProcessingException(ex); } } + public async Task GetSubmodelDescriptorByAasIdAsync(string aasId, string submodelId, CancellationToken cancellationToken) + { + await EnsureSubmodelBelongsToAasAsync( + aasId, + submodelId, + cancellationToken); + + return await submodelDescriptorService + .GetSubmodelDescriptorByIdAsync(submodelId, cancellationToken) + .ConfigureAwait(false); + } + public async Task GetAllSubmodelDescriptorsByAasIdAsync( + string aasId, + int? limit, + string? cursor, + CancellationToken cancellationToken) + { + var submodelRef = await aasRepositoryService + .GetSubmodelRefByIdAsync( + aasId, + null, + null, + cancellationToken) + .ConfigureAwait(false); + + var submodelIds = submodelRef.Result? + .SelectMany(r => r.Keys ?? []) + .Select(k => k.Value) + .Distinct(StringComparer.OrdinalIgnoreCase) + .ToList() ?? []; + + var descriptors = new List(); + + foreach (var submodelId in submodelIds) + { + var descriptor = await GetSubmodelDescriptorByAasIdAsync( + aasId, + submodelId, + cancellationToken); + + if (descriptor is not null) + { + descriptors.Add(descriptor); + } + } + + var (items, pagingMetaData) = + PagingExtensions.GetPagedResult( + descriptors, + d => d.Id!, + limit, + cursor); + + return new SubmodelDescriptors + { + Result = items, + PagingMetaData = pagingMetaData + }; + } + + private async Task EnsureSubmodelBelongsToAasAsync( + string aasId, + string submodelId, + CancellationToken cancellationToken) + { + var isReferenced = await aasRepositoryService + .IsSubmodelReferencedByAasAsync( + aasId, + submodelId, + cancellationToken); + + if (!isReferenced) + { + throw new SubmodelDescriptorNotFoundException(); + } + } private async Task TryBuildShellDescriptorAsync(ShellDescriptorMetaData shellDescriptorMetadata, CancellationToken cancellationToken) { diff --git a/source/AAS.TwinEngine.DataEngine/ApplicationLogic/Services/AasRepository/AasRepositoryService.cs b/source/AAS.TwinEngine.DataEngine/ApplicationLogic/Services/AasRepository/AasRepositoryService.cs index d3e9850f..e0933ca4 100644 --- a/source/AAS.TwinEngine.DataEngine/ApplicationLogic/Services/AasRepository/AasRepositoryService.cs +++ b/source/AAS.TwinEngine.DataEngine/ApplicationLogic/Services/AasRepository/AasRepositoryService.cs @@ -1,10 +1,13 @@ -using AAS.TwinEngine.DataEngine.ApplicationLogic.Exceptions.Application; +using AAS.TwinEngine.DataEngine.Api.SubmodelRepository.Requests; +using AAS.TwinEngine.DataEngine.ApplicationLogic.Exceptions.Application; using AAS.TwinEngine.DataEngine.ApplicationLogic.Exceptions.Infrastructure; using AAS.TwinEngine.DataEngine.ApplicationLogic.Extensions; using AAS.TwinEngine.DataEngine.ApplicationLogic.Services.Plugin; +using AAS.TwinEngine.DataEngine.ApplicationLogic.Services.SubmodelRepository; using AAS.TwinEngine.DataEngine.DomainModel.AasRegistry; using AAS.TwinEngine.DataEngine.DomainModel.AasRepository; using AAS.TwinEngine.DataEngine.DomainModel.Shared; +using AAS.TwinEngine.DataEngine.DomainModel.SubmodelRepository; using AAS.TwinEngine.DataEngine.ServiceConfiguration.Config; using AasCore.Aas3_1; @@ -20,6 +23,7 @@ public class AasRepositoryService( IAasRepositoryTemplateService templateService, IPluginDataHandler pluginDataHandler, IPluginManifestConflictHandler pluginManifestConflictHandler, + ISubmodelRepositoryService submodelRepositoryService, IOptions templateManagementConfig) : IAasRepositoryService { private readonly int _concurrentOperationsLimit = templateManagementConfig.Value.AasTemplateRepository.ConcurrentOperationsLimit; @@ -124,6 +128,51 @@ public async Task GetSubmodelRefByIdAsync(string aasIdentifier, int }; } + public async Task IsSubmodelReferencedByAasAsync(string aasIdentifier, string submodelIdentifier, CancellationToken cancellationToken) + { + var submodelRef = await GetSubmodelRefByIdAsync(aasIdentifier, null, null, cancellationToken).ConfigureAwait(false); + + return submodelRef.Result? + .SelectMany(r => r.Keys ?? []) + .Any(k => string.Equals(k.Value, submodelIdentifier, StringComparison.OrdinalIgnoreCase)) + ?? false; + } + + public async Task GetSubmodelByAasIdAsync(string aasId, string submodelId, Level level, Extent extent, CancellationToken cancellationToken) + { + await EnsureSubmodelBelongsToAasAsync(aasId, submodelId, cancellationToken).ConfigureAwait(false); + + var queryOptions = new SubmodelQueryOptions(level.ToString(), extent.ToString()); + + return await submodelRepositoryService.GetSubmodelAsync(submodelId, queryOptions, cancellationToken).ConfigureAwait(false); + } + + public async Task GetAllSubmodelElementsByAasIdAsync(string aasId, string submodelId, Level level, Extent extent, int? limit, string? cursor, CancellationToken cancellationToken) + { + await EnsureSubmodelBelongsToAasAsync(aasId, submodelId, cancellationToken).ConfigureAwait(false); + + var queryOptions = new SubmodelQueryOptions(level.ToString(), extent.ToString()); + + return await submodelRepositoryService.GetAllSubmodelElementsAsync(submodelId, queryOptions, limit, cursor, cancellationToken).ConfigureAwait(false); + } + + public async Task GetSubmodelElementByAasIdAsync(string aasId, string submodelId, string idShortPath, CancellationToken cancellationToken) + { + await EnsureSubmodelBelongsToAasAsync(aasId, submodelId, cancellationToken).ConfigureAwait(false); + + return await submodelRepositoryService.GetSubmodelElementAsync(submodelId, idShortPath, cancellationToken).ConfigureAwait(false); + } + + private async Task EnsureSubmodelBelongsToAasAsync(string aasId, string submodelId, CancellationToken cancellationToken) + { + var isReferenced = await IsSubmodelReferencedByAasAsync(aasId, submodelId, cancellationToken).ConfigureAwait(false); + if (!isReferenced) + { + logger.LogError("Submodel {SubmodelId} not referenced by AAS {AasId}", submodelId, aasId); + throw new SubmodelNotFoundException(submodelId); + } + } + private static IAssetInformation FillOutAssetInformation(IAssetInformation template, AssetData pluginData) { if (template is null) diff --git a/source/AAS.TwinEngine.DataEngine/ApplicationLogic/Services/AasRepository/IAasRepositoryService.cs b/source/AAS.TwinEngine.DataEngine/ApplicationLogic/Services/AasRepository/IAasRepositoryService.cs index f23a3660..56c3411a 100644 --- a/source/AAS.TwinEngine.DataEngine/ApplicationLogic/Services/AasRepository/IAasRepositoryService.cs +++ b/source/AAS.TwinEngine.DataEngine/ApplicationLogic/Services/AasRepository/IAasRepositoryService.cs @@ -1,6 +1,8 @@ +using AAS.TwinEngine.DataEngine.Api.SubmodelRepository.Requests; using AAS.TwinEngine.DataEngine.DomainModel.AasRegistry; using AAS.TwinEngine.DataEngine.DomainModel.AasRepository; using AAS.TwinEngine.DataEngine.DomainModel.Discovery; +using AAS.TwinEngine.DataEngine.DomainModel.SubmodelRepository; using AasCore.Aas3_1; @@ -15,4 +17,12 @@ public interface IAasRepositoryService Task GetAssetInformationByIdAsync(string aasIdentifier, CancellationToken cancellationToken); Task GetSubmodelRefByIdAsync(string aasIdentifier, int? limit, string? cursor, CancellationToken cancellationToken); + + Task IsSubmodelReferencedByAasAsync(string aasIdentifier, string submodelIdentifier, CancellationToken cancellationToken); + + Task GetSubmodelByAasIdAsync(string aasId, string submodelId, Level level, Extent extent, CancellationToken cancellationToken); + + Task GetAllSubmodelElementsByAasIdAsync(string aasId, string submodelId, Level level, Extent extent, int? limit, string? cursor, CancellationToken cancellationToken); + + Task GetSubmodelElementByAasIdAsync(string aasId, string submodelId, string idShortPath, CancellationToken cancellationToken); } From 01f0e33e783d4cd5e2e28661590b821760d6b04d Mon Sep 17 00:00:00 2001 From: Hardi Shah Date: Mon, 3 Aug 2026 17:38:57 +0530 Subject: [PATCH 02/25] Refactor submodel validation methods in AasRepositoryService and IAasRepositoryService --- .../AasRegistry/ShellDescriptorService.cs | 73 ++++--------------- .../AasRepository/AasRepositoryService.cs | 35 ++++----- .../AasRepository/IAasRepositoryService.cs | 8 +- 3 files changed, 31 insertions(+), 85 deletions(-) diff --git a/source/AAS.TwinEngine.DataEngine/ApplicationLogic/Services/AasRegistry/ShellDescriptorService.cs b/source/AAS.TwinEngine.DataEngine/ApplicationLogic/Services/AasRegistry/ShellDescriptorService.cs index b2bd9258..e5a8d214 100644 --- a/source/AAS.TwinEngine.DataEngine/ApplicationLogic/Services/AasRegistry/ShellDescriptorService.cs +++ b/source/AAS.TwinEngine.DataEngine/ApplicationLogic/Services/AasRegistry/ShellDescriptorService.cs @@ -100,58 +100,30 @@ public class ShellDescriptorService( throw new InternalDataProcessingException(ex); } } + public async Task GetSubmodelDescriptorByAasIdAsync(string aasId, string submodelId, CancellationToken cancellationToken) { - await EnsureSubmodelBelongsToAasAsync( - aasId, - submodelId, - cancellationToken); + await aasRepositoryService.ValidateSubmodelBelongsToAasAsync(aasId, submodelId, cancellationToken).ConfigureAwait(false); - return await submodelDescriptorService - .GetSubmodelDescriptorByIdAsync(submodelId, cancellationToken) - .ConfigureAwait(false); + return await submodelDescriptorService.GetSubmodelDescriptorByIdAsync(submodelId, cancellationToken).ConfigureAwait(false); } - public async Task GetAllSubmodelDescriptorsByAasIdAsync( - string aasId, - int? limit, - string? cursor, - CancellationToken cancellationToken) + + public async Task GetAllSubmodelDescriptorsByAasIdAsync(string aasId, int? limit, string? cursor, CancellationToken cancellationToken) { - var submodelRef = await aasRepositoryService - .GetSubmodelRefByIdAsync( - aasId, - null, - null, - cancellationToken) - .ConfigureAwait(false); + var submodelRefs = await aasRepositoryService.GetSubmodelRefByIdAsync(aasId, null, null, cancellationToken).ConfigureAwait(false); - var submodelIds = submodelRef.Result? - .SelectMany(r => r.Keys ?? []) - .Select(k => k.Value) + var submodelIds = submodelRefs.Result? + .SelectMany(reference => reference.Keys ?? []) + .Select(key => key.Value) .Distinct(StringComparer.OrdinalIgnoreCase) .ToList() ?? []; - var descriptors = new List(); - - foreach (var submodelId in submodelIds) - { - var descriptor = await GetSubmodelDescriptorByAasIdAsync( - aasId, - submodelId, - cancellationToken); - - if (descriptor is not null) - { - descriptors.Add(descriptor); - } - } + var descriptors = (await Task.WhenAll(submodelIds.Select(submodelId => + GetSubmodelDescriptorByAasIdAsync(aasId, submodelId, cancellationToken))).ConfigureAwait(false)) + .OfType() + .ToList(); - var (items, pagingMetaData) = - PagingExtensions.GetPagedResult( - descriptors, - d => d.Id!, - limit, - cursor); + var (items, pagingMetaData) = PagingExtensions.GetPagedResult(descriptors, descriptor => descriptor.Id!, limit, cursor); return new SubmodelDescriptors { @@ -160,23 +132,6 @@ await EnsureSubmodelBelongsToAasAsync( }; } - private async Task EnsureSubmodelBelongsToAasAsync( - string aasId, - string submodelId, - CancellationToken cancellationToken) - { - var isReferenced = await aasRepositoryService - .IsSubmodelReferencedByAasAsync( - aasId, - submodelId, - cancellationToken); - - if (!isReferenced) - { - throw new SubmodelDescriptorNotFoundException(); - } - } - private async Task TryBuildShellDescriptorAsync(ShellDescriptorMetaData shellDescriptorMetadata, CancellationToken cancellationToken) { try diff --git a/source/AAS.TwinEngine.DataEngine/ApplicationLogic/Services/AasRepository/AasRepositoryService.cs b/source/AAS.TwinEngine.DataEngine/ApplicationLogic/Services/AasRepository/AasRepositoryService.cs index e0933ca4..2500ce7d 100644 --- a/source/AAS.TwinEngine.DataEngine/ApplicationLogic/Services/AasRepository/AasRepositoryService.cs +++ b/source/AAS.TwinEngine.DataEngine/ApplicationLogic/Services/AasRepository/AasRepositoryService.cs @@ -128,51 +128,42 @@ public async Task GetSubmodelRefByIdAsync(string aasIdentifier, int }; } - public async Task IsSubmodelReferencedByAasAsync(string aasIdentifier, string submodelIdentifier, CancellationToken cancellationToken) - { - var submodelRef = await GetSubmodelRefByIdAsync(aasIdentifier, null, null, cancellationToken).ConfigureAwait(false); - - return submodelRef.Result? - .SelectMany(r => r.Keys ?? []) - .Any(k => string.Equals(k.Value, submodelIdentifier, StringComparison.OrdinalIgnoreCase)) - ?? false; - } - public async Task GetSubmodelByAasIdAsync(string aasId, string submodelId, Level level, Extent extent, CancellationToken cancellationToken) { - await EnsureSubmodelBelongsToAasAsync(aasId, submodelId, cancellationToken).ConfigureAwait(false); + await ValidateSubmodelBelongsToAasAsync(aasId, submodelId, cancellationToken).ConfigureAwait(false); - var queryOptions = new SubmodelQueryOptions(level.ToString(), extent.ToString()); - - return await submodelRepositoryService.GetSubmodelAsync(submodelId, queryOptions, cancellationToken).ConfigureAwait(false); + return await submodelRepositoryService.GetSubmodelAsync(submodelId, CreateQueryOptions(level, extent), cancellationToken).ConfigureAwait(false); } public async Task GetAllSubmodelElementsByAasIdAsync(string aasId, string submodelId, Level level, Extent extent, int? limit, string? cursor, CancellationToken cancellationToken) { - await EnsureSubmodelBelongsToAasAsync(aasId, submodelId, cancellationToken).ConfigureAwait(false); - - var queryOptions = new SubmodelQueryOptions(level.ToString(), extent.ToString()); + await ValidateSubmodelBelongsToAasAsync(aasId, submodelId, cancellationToken).ConfigureAwait(false); - return await submodelRepositoryService.GetAllSubmodelElementsAsync(submodelId, queryOptions, limit, cursor, cancellationToken).ConfigureAwait(false); + return await submodelRepositoryService.GetAllSubmodelElementsAsync(submodelId, CreateQueryOptions(level, extent), limit, cursor, cancellationToken).ConfigureAwait(false); } public async Task GetSubmodelElementByAasIdAsync(string aasId, string submodelId, string idShortPath, CancellationToken cancellationToken) { - await EnsureSubmodelBelongsToAasAsync(aasId, submodelId, cancellationToken).ConfigureAwait(false); + await ValidateSubmodelBelongsToAasAsync(aasId, submodelId, cancellationToken).ConfigureAwait(false); return await submodelRepositoryService.GetSubmodelElementAsync(submodelId, idShortPath, cancellationToken).ConfigureAwait(false); } - private async Task EnsureSubmodelBelongsToAasAsync(string aasId, string submodelId, CancellationToken cancellationToken) + public async Task ValidateSubmodelBelongsToAasAsync(string aasId, string submodelId, CancellationToken cancellationToken) { - var isReferenced = await IsSubmodelReferencedByAasAsync(aasId, submodelId, cancellationToken).ConfigureAwait(false); - if (!isReferenced) + var submodelRefs = await GetSubmodelRefByIdAsync(aasId, null, null, cancellationToken).ConfigureAwait(false); + + var submodelExists = submodelRefs.Result?.SelectMany(r => r.Keys ?? []) + .Any(k => string.Equals(k.Value, submodelId, StringComparison.OrdinalIgnoreCase)) ?? false; + + if (!submodelExists) { logger.LogError("Submodel {SubmodelId} not referenced by AAS {AasId}", submodelId, aasId); throw new SubmodelNotFoundException(submodelId); } } + private static SubmodelQueryOptions CreateQueryOptions(Level level, Extent extent) => new(level.ToString(), extent.ToString()); private static IAssetInformation FillOutAssetInformation(IAssetInformation template, AssetData pluginData) { if (template is null) diff --git a/source/AAS.TwinEngine.DataEngine/ApplicationLogic/Services/AasRepository/IAasRepositoryService.cs b/source/AAS.TwinEngine.DataEngine/ApplicationLogic/Services/AasRepository/IAasRepositoryService.cs index 56c3411a..29abe5df 100644 --- a/source/AAS.TwinEngine.DataEngine/ApplicationLogic/Services/AasRepository/IAasRepositoryService.cs +++ b/source/AAS.TwinEngine.DataEngine/ApplicationLogic/Services/AasRepository/IAasRepositoryService.cs @@ -18,11 +18,11 @@ public interface IAasRepositoryService Task GetSubmodelRefByIdAsync(string aasIdentifier, int? limit, string? cursor, CancellationToken cancellationToken); - Task IsSubmodelReferencedByAasAsync(string aasIdentifier, string submodelIdentifier, CancellationToken cancellationToken); + Task ValidateSubmodelBelongsToAasAsync(string aasIdentifier, string submodelIdentifier, CancellationToken cancellationToken); - Task GetSubmodelByAasIdAsync(string aasId, string submodelId, Level level, Extent extent, CancellationToken cancellationToken); + Task GetSubmodelByAasIdAsync(string aasIdentifier, string submodelIdentifier, Level level, Extent extent, CancellationToken cancellationToken); - Task GetAllSubmodelElementsByAasIdAsync(string aasId, string submodelId, Level level, Extent extent, int? limit, string? cursor, CancellationToken cancellationToken); + Task GetAllSubmodelElementsByAasIdAsync(string aasIdentifier, string submodelIdentifier, Level level, Extent extent, int? limit, string? cursor, CancellationToken cancellationToken); - Task GetSubmodelElementByAasIdAsync(string aasId, string submodelId, string idShortPath, CancellationToken cancellationToken); + Task GetSubmodelElementByAasIdAsync(string aasIdentifier, string submodelIdentifier, string idShortPath, CancellationToken cancellationToken); } From 68854720bcba6e23b0ed553f8bf502ffa77029b8 Mon Sep 17 00:00:00 2001 From: Hardi Shah Date: Tue, 4 Aug 2026 12:01:16 +0530 Subject: [PATCH 03/25] Refactor submodel queries to use SubmodelQueryOptions Centralize submodel query logic by introducing SubmodelQueryOptions for Level and Extent parameters. Add explicit validation for submodel ownership via ValidateSubmodelBelongsToAasAsync. Update handlers, services, unit tests, and .bru API definitions to use the new approach. Remove obsolete helpers and improve code clarity and maintainability. --- ...del Descriptor by submodelId and aasId.bru | 21 +++ .../Get Submodel Descriptors by aasId.bru | 20 +++ .../Product1/Get Shell By Id.bru | 2 +- .../Get Shells By Asset Id - Product1.bru | 4 +- .../Product1/Get Submodel Ref By Id.bru | 2 +- ...Get Submodel by submodelId and aaslId.bru} | 4 +- ... Submodel-element by idshort and aasID.bru | 22 +++ .../Get Submodel-elements by aasID.bru | 21 +++ ...lls By Asset Id and IdShort - Product2.bru | 4 +- .../Product2/Get Submodel Ref By Id.bru | 2 +- ... Get Submodel by submodelId and aasId.bru} | 4 +- ... Submodel-element by idshort and aasID.bru | 22 +++ .../Get Submodel-elements by aasID.bru | 21 +++ .../Get Submodel by submodelId and aasId.bru | 26 +++ ... Submodel-element by idshort and aasID.bru | 22 +++ .../Get Submodel-elements by aasID.bru | 21 +++ .../ShellDescriptorControllerTests.cs | 105 +++++++----- .../AasRepositoryControllerTests.cs | 39 +++-- .../Handler/ShellDescriptorHandlerTests.cs | 48 ++---- .../AasRepositoryControllerTests.cs | 4 +- .../Handler/AasRepositoryHandlerTests.cs | 150 +++++++++++++----- .../ShellDescriptorServiceTests.cs | 71 ++++++++- .../AasRepositoryServiceTests.cs | 72 ++++++++- .../Handler/AasRepositoryHandler.cs | 8 +- .../AasRepository/AasRepositoryService.cs | 32 ++-- .../AasRepository/IAasRepositoryService.cs | 9 +- 26 files changed, 593 insertions(+), 163 deletions(-) create mode 100644 example/apiCollection/Aas Registry/Get Submodel Descriptor by submodelId and aasId.bru create mode 100644 example/apiCollection/Aas Registry/Get Submodel Descriptors by aasId.bru rename example/apiCollection/Aas Repository/Product1/{Get Submodel by submodelId and shellId.bru => Get Submodel by submodelId and aaslId.bru} (72%) create mode 100644 example/apiCollection/Aas Repository/Product1/Get Submodel-element by idshort and aasID.bru create mode 100644 example/apiCollection/Aas Repository/Product1/Get Submodel-elements by aasID.bru rename example/apiCollection/Aas Repository/Product2/{Get Submodel by submodelId and shellId.bru => Get Submodel by submodelId and aasId.bru} (73%) create mode 100644 example/apiCollection/Aas Repository/Product2/Get Submodel-element by idshort and aasID.bru create mode 100644 example/apiCollection/Aas Repository/Product2/Get Submodel-elements by aasID.bru create mode 100644 example/apiCollection/Aas Repository/Product3/Get Submodel by submodelId and aasId.bru create mode 100644 example/apiCollection/Aas Repository/Product3/Get Submodel-element by idshort and aasID.bru create mode 100644 example/apiCollection/Aas Repository/Product3/Get Submodel-elements by aasID.bru diff --git a/example/apiCollection/Aas Registry/Get Submodel Descriptor by submodelId and aasId.bru b/example/apiCollection/Aas Registry/Get Submodel Descriptor by submodelId and aasId.bru new file mode 100644 index 00000000..ce39af77 --- /dev/null +++ b/example/apiCollection/Aas Registry/Get Submodel Descriptor by submodelId and aasId.bru @@ -0,0 +1,21 @@ +meta { + name: Get Submodel Descriptor by submodelId and aasId + type: http + seq: 6 +} + +get { + url: {{DataEngineBaseUrl}}/shell-descriptors/:aasIdentifier/submodel-descriptors/:submodelIdentifier + body: none + auth: inherit +} + +params:path { + submodelIdentifier: {{submodelIdentifierCarbonFootprint-1}} + aasIdentifier: {{aasIdentifier-1}} +} + +settings { + encodeUrl: true + timeout: 0 +} diff --git a/example/apiCollection/Aas Registry/Get Submodel Descriptors by aasId.bru b/example/apiCollection/Aas Registry/Get Submodel Descriptors by aasId.bru new file mode 100644 index 00000000..e918ec90 --- /dev/null +++ b/example/apiCollection/Aas Registry/Get Submodel Descriptors by aasId.bru @@ -0,0 +1,20 @@ +meta { + name: Get Submodel Descriptors by aasId + type: http + seq: 5 +} + +get { + url: {{DataEngineBaseUrl}}/shell-descriptors/:aasIdentifier/submodel-descriptors + body: none + auth: inherit +} + +params:path { + aasIdentifier: {{aasIdentifier-1}} +} + +settings { + encodeUrl: true + timeout: 0 +} diff --git a/example/apiCollection/Aas Repository/Product1/Get Shell By Id.bru b/example/apiCollection/Aas Repository/Product1/Get Shell By Id.bru index 2f49754b..9f764e9f 100644 --- a/example/apiCollection/Aas Repository/Product1/Get Shell By Id.bru +++ b/example/apiCollection/Aas Repository/Product1/Get Shell By Id.bru @@ -1,7 +1,7 @@ meta { name: Get Shell By Id type: http - seq: 1 + seq: 2 } get { diff --git a/example/apiCollection/Aas Repository/Product1/Get Shells By Asset Id - Product1.bru b/example/apiCollection/Aas Repository/Product1/Get Shells By Asset Id - Product1.bru index f55bf5ff..9c352a3e 100644 --- a/example/apiCollection/Aas Repository/Product1/Get Shells By Asset Id - Product1.bru +++ b/example/apiCollection/Aas Repository/Product1/Get Shells By Asset Id - Product1.bru @@ -1,7 +1,7 @@ meta { name: Get Shells By Asset Id - Product1 type: http - seq: 2 + seq: 3 } get { @@ -12,7 +12,7 @@ get { params:query { assetIds: {{assetId-product1}} - idShort: + idShort: limit: cursor: } diff --git a/example/apiCollection/Aas Repository/Product1/Get Submodel Ref By Id.bru b/example/apiCollection/Aas Repository/Product1/Get Submodel Ref By Id.bru index fad39781..737631d4 100644 --- a/example/apiCollection/Aas Repository/Product1/Get Submodel Ref By Id.bru +++ b/example/apiCollection/Aas Repository/Product1/Get Submodel Ref By Id.bru @@ -1,7 +1,7 @@ meta { name: Get Submodel Ref By Id type: http - seq: 2 + seq: 4 } get { diff --git a/example/apiCollection/Aas Repository/Product1/Get Submodel by submodelId and shellId.bru b/example/apiCollection/Aas Repository/Product1/Get Submodel by submodelId and aaslId.bru similarity index 72% rename from example/apiCollection/Aas Repository/Product1/Get Submodel by submodelId and shellId.bru rename to example/apiCollection/Aas Repository/Product1/Get Submodel by submodelId and aaslId.bru index fe76a11f..2c901ad8 100644 --- a/example/apiCollection/Aas Repository/Product1/Get Submodel by submodelId and shellId.bru +++ b/example/apiCollection/Aas Repository/Product1/Get Submodel by submodelId and aaslId.bru @@ -1,5 +1,5 @@ meta { - name: Get Submodel by submodelId and shellId + name: Get Submodel by submodelId and aaslId type: http seq: 5 } @@ -16,7 +16,7 @@ params:query { } params:path { - submodelIdentifier: {{submodelIdentifierCarbonFootprint-1}} + submodelIdentifier: {{submodelIdentifierNameplate-1}} aasIdentifier: {{aasIdentifier-1}} } diff --git a/example/apiCollection/Aas Repository/Product1/Get Submodel-element by idshort and aasID.bru b/example/apiCollection/Aas Repository/Product1/Get Submodel-element by idshort and aasID.bru new file mode 100644 index 00000000..bb8499b6 --- /dev/null +++ b/example/apiCollection/Aas Repository/Product1/Get Submodel-element by idshort and aasID.bru @@ -0,0 +1,22 @@ +meta { + name: Get Submodel-element by idshort and aasID + type: http + seq: 7 +} + +get { + url: {{DataEngineBaseUrl}}/shells/:aasIdentifier/submodels/:submodelIdentifier/submodel-elements/:idShortPath + body: none + auth: inherit +} + +params:path { + idShortPath: ManufacturerName + aasIdentifier: {{aasIdentifier-1}} + submodelIdentifier: {{submodelIdentifierNameplate-1}} +} + +settings { + encodeUrl: true + timeout: 0 +} diff --git a/example/apiCollection/Aas Repository/Product1/Get Submodel-elements by aasID.bru b/example/apiCollection/Aas Repository/Product1/Get Submodel-elements by aasID.bru new file mode 100644 index 00000000..3113da34 --- /dev/null +++ b/example/apiCollection/Aas Repository/Product1/Get Submodel-elements by aasID.bru @@ -0,0 +1,21 @@ +meta { + name: Get Submodel-elements by aasID + type: http + seq: 6 +} + +get { + url: {{DataEngineBaseUrl}}/shells/:aasIdentifier/submodels/:submodelIdentifier/submodel-elements + body: none + auth: inherit +} + +params:path { + aasIdentifier: {{aasIdentifier-1}} + submodelIdentifier: {{submodelIdentifierNameplate-1}} +} + +settings { + encodeUrl: true + timeout: 0 +} diff --git a/example/apiCollection/Aas Repository/Product2/Get Shells By Asset Id and IdShort - Product2.bru b/example/apiCollection/Aas Repository/Product2/Get Shells By Asset Id and IdShort - Product2.bru index 5b04aa38..1aef7d90 100644 --- a/example/apiCollection/Aas Repository/Product2/Get Shells By Asset Id and IdShort - Product2.bru +++ b/example/apiCollection/Aas Repository/Product2/Get Shells By Asset Id and IdShort - Product2.bru @@ -1,7 +1,7 @@ meta { name: Get Shells By Asset Id and IdShort - Product2 type: http - seq: 7 + seq: 5 } get { @@ -25,4 +25,4 @@ settings { docs { Retrieves shells filtered by specific asset ID and idShort. The idShort value is URL-encoded in the collection pre-request script. -} \ No newline at end of file +} diff --git a/example/apiCollection/Aas Repository/Product2/Get Submodel Ref By Id.bru b/example/apiCollection/Aas Repository/Product2/Get Submodel Ref By Id.bru index 43cd8eae..65019fbd 100644 --- a/example/apiCollection/Aas Repository/Product2/Get Submodel Ref By Id.bru +++ b/example/apiCollection/Aas Repository/Product2/Get Submodel Ref By Id.bru @@ -1,7 +1,7 @@ meta { name: Get Submodel Ref By Id type: http - seq: 3 + seq: 4 } get { diff --git a/example/apiCollection/Aas Repository/Product2/Get Submodel by submodelId and shellId.bru b/example/apiCollection/Aas Repository/Product2/Get Submodel by submodelId and aasId.bru similarity index 73% rename from example/apiCollection/Aas Repository/Product2/Get Submodel by submodelId and shellId.bru rename to example/apiCollection/Aas Repository/Product2/Get Submodel by submodelId and aasId.bru index 9ef0ea40..9b640de3 100644 --- a/example/apiCollection/Aas Repository/Product2/Get Submodel by submodelId and shellId.bru +++ b/example/apiCollection/Aas Repository/Product2/Get Submodel by submodelId and aasId.bru @@ -1,5 +1,5 @@ meta { - name: Get Submodel by submodelId and shellId + name: Get Submodel by submodelId and aasId type: http seq: 6 } @@ -16,7 +16,7 @@ params:query { } params:path { - submodelIdentifier: {{submodelIdentifierNameplate-2}} + submodelIdentifier: {{submodelIdentifierNameplate-1}} aasIdentifier: {{aasIdentifier-2}} } diff --git a/example/apiCollection/Aas Repository/Product2/Get Submodel-element by idshort and aasID.bru b/example/apiCollection/Aas Repository/Product2/Get Submodel-element by idshort and aasID.bru new file mode 100644 index 00000000..2b346646 --- /dev/null +++ b/example/apiCollection/Aas Repository/Product2/Get Submodel-element by idshort and aasID.bru @@ -0,0 +1,22 @@ +meta { + name: Get Submodel-element by idshort and aasID + type: http + seq: 8 +} + +get { + url: {{DataEngineBaseUrl}}/shells/:aasIdentifier/submodels/:submodelIdentifier/submodel-elements/:idShortPath + body: none + auth: inherit +} + +params:path { + idShortPath: ManufacturerName + aasIdentifier: {{aasIdentifier-2}} + submodelIdentifier: {{submodelIdentifierNameplate-2}} +} + +settings { + encodeUrl: true + timeout: 0 +} diff --git a/example/apiCollection/Aas Repository/Product2/Get Submodel-elements by aasID.bru b/example/apiCollection/Aas Repository/Product2/Get Submodel-elements by aasID.bru new file mode 100644 index 00000000..d1127f44 --- /dev/null +++ b/example/apiCollection/Aas Repository/Product2/Get Submodel-elements by aasID.bru @@ -0,0 +1,21 @@ +meta { + name: Get Submodel-elements by aasID + type: http + seq: 7 +} + +get { + url: {{DataEngineBaseUrl}}/shells/:aasIdentifier/submodels/:submodelIdentifier/submodel-elements + body: none + auth: inherit +} + +params:path { + aasIdentifier: {{aasIdentifier-2}} + submodelIdentifier: {{submodelIdentifierNameplate-2}} +} + +settings { + encodeUrl: true + timeout: 0 +} diff --git a/example/apiCollection/Aas Repository/Product3/Get Submodel by submodelId and aasId.bru b/example/apiCollection/Aas Repository/Product3/Get Submodel by submodelId and aasId.bru new file mode 100644 index 00000000..3fdb6341 --- /dev/null +++ b/example/apiCollection/Aas Repository/Product3/Get Submodel by submodelId and aasId.bru @@ -0,0 +1,26 @@ +meta { + name: Get Submodel by submodelId and aasId + type: http + seq: 4 +} + +get { + url: {{DataEngineBaseUrl}}/shells/:aasIdentifier/submodels/:submodelIdentifier + body: none + auth: inherit +} + +params:query { + ~level: + ~extent: +} + +params:path { + submodelIdentifier: {{submodelIdentifierNameplate-3}} + aasIdentifier: {{aasIdentifier-3}} +} + +settings { + encodeUrl: true + timeout: 0 +} diff --git a/example/apiCollection/Aas Repository/Product3/Get Submodel-element by idshort and aasID.bru b/example/apiCollection/Aas Repository/Product3/Get Submodel-element by idshort and aasID.bru new file mode 100644 index 00000000..e8c4349c --- /dev/null +++ b/example/apiCollection/Aas Repository/Product3/Get Submodel-element by idshort and aasID.bru @@ -0,0 +1,22 @@ +meta { + name: Get Submodel-element by idshort and aasID + type: http + seq: 6 +} + +get { + url: {{DataEngineBaseUrl}}/shells/:aasIdentifier/submodels/:submodelIdentifier/submodel-elements/:idShortPath + body: none + auth: inherit +} + +params:path { + idShortPath: ManufacturerName + aasIdentifier: {{aasIdentifier-3}} + submodelIdentifier: {{submodelIdentifierNameplate-3}} +} + +settings { + encodeUrl: true + timeout: 0 +} diff --git a/example/apiCollection/Aas Repository/Product3/Get Submodel-elements by aasID.bru b/example/apiCollection/Aas Repository/Product3/Get Submodel-elements by aasID.bru new file mode 100644 index 00000000..e49455f8 --- /dev/null +++ b/example/apiCollection/Aas Repository/Product3/Get Submodel-elements by aasID.bru @@ -0,0 +1,21 @@ +meta { + name: Get Submodel-elements by aasID + type: http + seq: 5 +} + +get { + url: {{DataEngineBaseUrl}}/shells/:aasIdentifier/submodels/:submodelIdentifier/submodel-elements + body: none + auth: inherit +} + +params:path { + aasIdentifier: {{aasIdentifier-3}} + submodelIdentifier: {{submodelIdentifierNameplate-3}} +} + +settings { + encodeUrl: true + timeout: 0 +} diff --git a/source/AAS.TwinEngine.DataEngine.ModuleTests/Api/Services/AasRegistry/ShellDescriptorControllerTests.cs b/source/AAS.TwinEngine.DataEngine.ModuleTests/Api/Services/AasRegistry/ShellDescriptorControllerTests.cs index 9793074e..a32630da 100644 --- a/source/AAS.TwinEngine.DataEngine.ModuleTests/Api/Services/AasRegistry/ShellDescriptorControllerTests.cs +++ b/source/AAS.TwinEngine.DataEngine.ModuleTests/Api/Services/AasRegistry/ShellDescriptorControllerTests.cs @@ -449,22 +449,23 @@ public async Task GetAllSubmodelDescriptorsByAasIdAsync_ReturnsOkAsync() { // Arrange const string AasIdentifier = "aHR0cHM6Ly9leGFtcGxlLmNvbS9pZHMvYWFzLzExNzBfMTE2MF8zMDUyXzY1Njg="; - const string SubmodelId = "http://example.com/submodel/Nameplate"; - - var submodelRefs = new List - { - new Reference( - ReferenceTypes.ModelReference, - [new Key(KeyTypes.Submodel, SubmodelId)], - null) - }; + const string SubmodelKey = "Nameplate"; + const string ProductId = "1170_1160_3052_6568"; + const string SubmodelTemplateId = "https://admin-shell.io/idta/SubmodelTemplate/DigitalNameplate/3/0"; + var submodelId = $"https://mm-software.com/submodel/{ProductId}/{SubmodelKey}"; _ = _mockTemplateProvider.GetSubmodelRefByIdAsync(Arg.Any(), Arg.Any()) - .Returns(submodelRefs); + .Returns(_ => + [ + new Reference( + ReferenceTypes.ModelReference, + [new Key(KeyTypes.Submodel, SubmodelKey)], + null) + ]); _ = _mockSubmodelDescriptorProvider - .GetDataForSubmodelDescriptorByIdAsync(SubmodelId, Arg.Any()) - .Returns(new SubmodelDescriptor { Id = SubmodelId, Endpoints = [] }); + .GetDataForSubmodelDescriptorByIdAsync(SubmodelTemplateId, Arg.Any()) + .Returns(new SubmodelDescriptor { Id = submodelId, Endpoints = [] }); // Act var response = await _client.GetAsync($"/shell-descriptors/{AasIdentifier}/submodel-descriptors"); @@ -483,23 +484,24 @@ public async Task GetSubmodelDescriptorByAasIdAsync_ReturnsOkAsync() { // Arrange const string AasIdentifier = "aHR0cHM6Ly9leGFtcGxlLmNvbS9pZHMvYWFzLzExNzBfMTE2MF8zMDUyXzY1Njg="; - const string SubmodelId = "http://example.com/submodel/Nameplate"; - var encodedSubmodelId = EncodeBase64Url(SubmodelId); - - var submodelRefs = new List - { - new Reference( - ReferenceTypes.ModelReference, - [new Key(KeyTypes.Submodel, SubmodelId)], - null) - }; + const string SubmodelKey = "Nameplate"; + const string ProductId = "1170_1160_3052_6568"; + const string SubmodelTemplateId = "https://admin-shell.io/idta/SubmodelTemplate/DigitalNameplate/3/0"; + var submodelId = $"https://mm-software.com/submodel/{ProductId}/{SubmodelKey}"; + var encodedSubmodelId = EncodeBase64Url(submodelId); _ = _mockTemplateProvider.GetSubmodelRefByIdAsync(Arg.Any(), Arg.Any()) - .Returns(submodelRefs); + .Returns(_ => + [ + new Reference( + ReferenceTypes.ModelReference, + [new Key(KeyTypes.Submodel, SubmodelKey)], + null) + ]); _ = _mockSubmodelDescriptorProvider - .GetDataForSubmodelDescriptorByIdAsync(SubmodelId, Arg.Any()) - .Returns(new SubmodelDescriptor { Id = SubmodelId, Endpoints = [] }); + .GetDataForSubmodelDescriptorByIdAsync(SubmodelTemplateId, Arg.Any()) + .Returns(new SubmodelDescriptor { Id = submodelId, Endpoints = [] }); // Act var response = await _client.GetAsync($"/shell-descriptors/{AasIdentifier}/submodel-descriptors/{encodedSubmodelId}"); @@ -515,20 +517,18 @@ public async Task GetSubmodelDescriptorByAasIdAsync_WhenSubmodelNotInAas_Returns { // Arrange const string AasIdentifier = "aHR0cHM6Ly9leGFtcGxlLmNvbS9pZHMvYWFzLzExNzBfMTE2MF8zMDUyXzY1Njg="; - const string OtherSubmodelId = "http://example.com/submodel/Other"; - const string RequestedSubmodelId = "http://example.com/submodel/Missing"; - var encodedSubmodelId = EncodeBase64Url(RequestedSubmodelId); - - var submodelRefs = new List - { - new Reference( - ReferenceTypes.ModelReference, - [new Key(KeyTypes.Submodel, OtherSubmodelId)], - null) - }; + const string ProductId = "1170_1160_3052_6568"; + var requestedSubmodelId = $"https://mm-software.com/submodel/{ProductId}/Missing"; + var encodedSubmodelId = EncodeBase64Url(requestedSubmodelId); _ = _mockTemplateProvider.GetSubmodelRefByIdAsync(Arg.Any(), Arg.Any()) - .Returns(submodelRefs); + .Returns(_ => + [ + new Reference( + ReferenceTypes.ModelReference, + [new Key(KeyTypes.Submodel, "Other")], + null) + ]); // Act var response = await _client.GetAsync($"/shell-descriptors/{AasIdentifier}/submodel-descriptors/{encodedSubmodelId}"); @@ -537,6 +537,37 @@ [new Key(KeyTypes.Submodel, OtherSubmodelId)], Assert.Equal(HttpStatusCode.NotFound, response.StatusCode); } + [Fact] + public async Task GetSubmodelDescriptorByAasIdAsync_WithDifferentCaseInSubmodelId_ReturnsOkAsync() + { + // Arrange + const string aasIdentifier = "aHR0cHM6Ly9leGFtcGxlLmNvbS9pZHMvYWFzLzExNzBfMTE2MF8zMDUyXzY1Njg="; + const string submodelKey = "Nameplate"; + const string productId = "1170_1160_3052_6568"; + const string submodelTemplateId = "https://admin-shell.io/idta/SubmodelTemplate/DigitalNameplate/3/0"; + var requestedSubmodelId = $"https://mm-software.com/submodel/{productId}/nameplate"; + var encodedSubmodelId = EncodeBase64Url(requestedSubmodelId); + + _ = _mockTemplateProvider.GetSubmodelRefByIdAsync(Arg.Any(), Arg.Any()) + .Returns(_ => + [ + new Reference( + ReferenceTypes.ModelReference, + [new Key(KeyTypes.Submodel, submodelKey)], + null) + ]); + + _ = _mockSubmodelDescriptorProvider + .GetDataForSubmodelDescriptorByIdAsync(submodelTemplateId, Arg.Any()) + .Returns(new SubmodelDescriptor { Id = requestedSubmodelId, Endpoints = [] }); + + // Act + var response = await _client.GetAsync($"/shell-descriptors/{aasIdentifier}/submodel-descriptors/{encodedSubmodelId}"); + + // Assert + Assert.Equal(HttpStatusCode.OK, response.StatusCode); + } + private static string EncodeBase64Url(string plainText) { if (string.IsNullOrWhiteSpace(plainText)) diff --git a/source/AAS.TwinEngine.DataEngine.ModuleTests/Api/Services/AasRepository/AasRepositoryControllerTests.cs b/source/AAS.TwinEngine.DataEngine.ModuleTests/Api/Services/AasRepository/AasRepositoryControllerTests.cs index 93a63d04..45d78a51 100644 --- a/source/AAS.TwinEngine.DataEngine.ModuleTests/Api/Services/AasRepository/AasRepositoryControllerTests.cs +++ b/source/AAS.TwinEngine.DataEngine.ModuleTests/Api/Services/AasRepository/AasRepositoryControllerTests.cs @@ -560,15 +560,17 @@ public async Task GetSubmodelByAasIdAsync_ReturnsOkAsync() { // Arrange const string AasIdentifier = "aHR0cHM6Ly9leGFtcGxlLmNvbS9pZHMvYWFzLzExNzBfMTE2MF8zMDUyXzY1Njg="; - const string SubmodelId = "http://example.com/submodel/Nameplate"; - var encodedSubmodelId = EncodeBase64Url(SubmodelId); + const string submodelKey = "Nameplate"; + const string productId = "1170_1160_3052_6568"; + var requestedSubmodelId = $"https://mm-software.com/submodel/{productId}/{submodelKey}"; + var encodedSubmodelId = EncodeBase64Url(requestedSubmodelId); _ = _mockTemplateProvider.GetSubmodelRefByIdAsync(Arg.Any(), Arg.Any()) - .Returns([new Reference(ReferenceTypes.ModelReference, [new Key(KeyTypes.Submodel, SubmodelId)], null)]); + .Returns([new Reference(ReferenceTypes.ModelReference, [new Key(KeyTypes.Submodel, submodelKey)], null)]); _ = _mockSubmodelRepositoryService - .GetSubmodelByAasIdAsync(SubmodelId, Arg.Any(), Arg.Any()) - .Returns(new Submodel(id: SubmodelId)); + .GetSubmodelAsync(requestedSubmodelId, Arg.Any(), Arg.Any()) + .Returns(new Submodel(id: requestedSubmodelId)); // Act var response = await _client.GetAsync($"/shells/{AasIdentifier}/submodels/{encodedSubmodelId}"); @@ -584,11 +586,12 @@ public async Task GetSubmodelByAasIdAsync_WhenSubmodelNotInAas_Returns404Async() { // Arrange const string AasIdentifier = "aHR0cHM6Ly9leGFtcGxlLmNvbS9pZHMvYWFzLzExNzBfMTE2MF8zMDUyXzY1Njg="; - const string RequestedSubmodelId = "http://example.com/submodel/Missing"; - var encodedSubmodelId = EncodeBase64Url(RequestedSubmodelId); + const string productId = "1170_1160_3052_6568"; + var requestedSubmodelId = $"https://mm-software.com/submodel/{productId}/Missing"; + var encodedSubmodelId = EncodeBase64Url(requestedSubmodelId); _ = _mockTemplateProvider.GetSubmodelRefByIdAsync(Arg.Any(), Arg.Any()) - .Returns([new Reference(ReferenceTypes.ModelReference, [new Key(KeyTypes.Submodel, "http://example.com/submodel/Other")], null)]); + .Returns([new Reference(ReferenceTypes.ModelReference, [new Key(KeyTypes.Submodel, "Other")], null)]); // Act var response = await _client.GetAsync($"/shells/{AasIdentifier}/submodels/{encodedSubmodelId}"); @@ -602,14 +605,16 @@ public async Task GetAllSubmodelElementsByAasIdAsync_ReturnsOkAsync() { // Arrange const string AasIdentifier = "aHR0cHM6Ly9leGFtcGxlLmNvbS9pZHMvYWFzLzExNzBfMTE2MF8zMDUyXzY1Njg="; - const string SubmodelId = "http://example.com/submodel/Nameplate"; - var encodedSubmodelId = EncodeBase64Url(SubmodelId); + const string submodelKey = "Nameplate"; + const string productId = "1170_1160_3052_6568"; + var requestedSubmodelId = $"https://mm-software.com/submodel/{productId}/{submodelKey}"; + var encodedSubmodelId = EncodeBase64Url(requestedSubmodelId); _ = _mockTemplateProvider.GetSubmodelRefByIdAsync(Arg.Any(), Arg.Any()) - .Returns([new Reference(ReferenceTypes.ModelReference, [new Key(KeyTypes.Submodel, SubmodelId)], null)]); + .Returns([new Reference(ReferenceTypes.ModelReference, [new Key(KeyTypes.Submodel, submodelKey)], null)]); _ = _mockSubmodelRepositoryService - .GetAllSubmodelElementsAsync(SubmodelId, Arg.Any(), Arg.Any(), Arg.Any(), Arg.Any()) + .GetAllSubmodelElementsAsync(requestedSubmodelId, Arg.Any(), Arg.Any(), Arg.Any(), Arg.Any()) .Returns(new SubmodelElementsPage { PagingMetaData = new PagingMetaData { Cursor = null }, Result = [] }); // Act @@ -627,15 +632,17 @@ public async Task GetSubmodelElementByAasIdAsync_ReturnsOkAsync() { // Arrange const string AasIdentifier = "aHR0cHM6Ly9leGFtcGxlLmNvbS9pZHMvYWFzLzExNzBfMTE2MF8zMDUyXzY1Njg="; - const string SubmodelId = "http://example.com/submodel/Nameplate"; + const string submodelKey = "Nameplate"; + const string productId = "1170_1160_3052_6568"; + var requestedSubmodelId = $"https://mm-software.com/submodel/{productId}/{submodelKey}"; const string IdShortPath = "ManufacturerName"; - var encodedSubmodelId = EncodeBase64Url(SubmodelId); + var encodedSubmodelId = EncodeBase64Url(requestedSubmodelId); _ = _mockTemplateProvider.GetSubmodelRefByIdAsync(Arg.Any(), Arg.Any()) - .Returns([new Reference(ReferenceTypes.ModelReference, [new Key(KeyTypes.Submodel, SubmodelId)], null)]); + .Returns([new Reference(ReferenceTypes.ModelReference, [new Key(KeyTypes.Submodel, submodelKey)], null)]); _ = _mockSubmodelRepositoryService - .GetSubmodelElementAsync(SubmodelId, IdShortPath, Arg.Any()) + .GetSubmodelElementAsync(requestedSubmodelId, IdShortPath, Arg.Any()) .Returns(new Property(DataTypeDefXsd.String) { IdShort = IdShortPath }); // Act diff --git a/source/AAS.TwinEngine.DataEngine.UnitTests/Api/AasRegistry/Handler/ShellDescriptorHandlerTests.cs b/source/AAS.TwinEngine.DataEngine.UnitTests/Api/AasRegistry/Handler/ShellDescriptorHandlerTests.cs index e2c2119e..dc57105c 100644 --- a/source/AAS.TwinEngine.DataEngine.UnitTests/Api/AasRegistry/Handler/ShellDescriptorHandlerTests.cs +++ b/source/AAS.TwinEngine.DataEngine.UnitTests/Api/AasRegistry/Handler/ShellDescriptorHandlerTests.cs @@ -123,16 +123,14 @@ public async Task GetSubmodelDescriptorByAasId_ReturnsDescriptor_WhenSubmodelBel const string AasId = "AasId"; const string SubmodelId = "SubmodelId"; var request = new GetSubmodelDescriptorByAasRequest(AasId.EncodeBase64Url(), SubmodelId.EncodeBase64Url()); - _aasRepositoryService.GetSubmodelRefByIdAsync(AasId, null, null, Arg.Any()) - .Returns(CreateSubmodelRefWithId(SubmodelId)); - _submodelDescriptorService.GetSubmodelDescriptorByIdAsync(SubmodelId, Arg.Any()) + _shellDescriptorService.GetSubmodelDescriptorByAasIdAsync(AasId, SubmodelId, Arg.Any()) .Returns(new SubmodelDescriptor { Id = SubmodelId }); var result = await _sut.GetSubmodelDescriptorByAasId(request, CancellationToken.None); Assert.IsType(result); - await _submodelDescriptorService.Received(1) - .GetSubmodelDescriptorByIdAsync(SubmodelId, Arg.Any()); + await _shellDescriptorService.Received(1) + .GetSubmodelDescriptorByAasIdAsync(AasId, SubmodelId, Arg.Any()); } [Fact] @@ -141,8 +139,8 @@ public async Task GetSubmodelDescriptorByAasId_SubmodelNotInAas_ThrowsSubmodelDe const string AasId = "AasId"; const string SubmodelId = "SubmodelId"; var request = new GetSubmodelDescriptorByAasRequest(AasId.EncodeBase64Url(), SubmodelId.EncodeBase64Url()); - _aasRepositoryService.GetSubmodelRefByIdAsync(AasId, null, null, Arg.Any()) - .Returns(CreateSubmodelRefWithId("OtherSubmodelId")); + _shellDescriptorService.GetSubmodelDescriptorByAasIdAsync(AasId, SubmodelId, Arg.Any()) + .ThrowsAsync(new SubmodelDescriptorNotFoundException()); await Assert.ThrowsAsync( () => _sut.GetSubmodelDescriptorByAasId(request, CancellationToken.None)); @@ -154,7 +152,7 @@ public async Task GetSubmodelDescriptorByAasId_AasNotFound_PropagatesException() const string AasId = "AasId"; const string SubmodelId = "SubmodelId"; var request = new GetSubmodelDescriptorByAasRequest(AasId.EncodeBase64Url(), SubmodelId.EncodeBase64Url()); - _aasRepositoryService.GetSubmodelRefByIdAsync(AasId, null, null, Arg.Any()) + _shellDescriptorService.GetSubmodelDescriptorByAasIdAsync(AasId, SubmodelId, Arg.Any()) .ThrowsAsync(new TemplateNotFoundException()); await Assert.ThrowsAsync( @@ -165,15 +163,17 @@ await Assert.ThrowsAsync( public async Task GetAllSubmodelDescriptorsByAasId_ReturnsDescriptors_ForAllSubmodelsInAas() { const string AasId = "AasId"; - const string SubmodelId1 = "SubmodelId1"; - const string SubmodelId2 = "SubmodelId2"; var request = new GetSubmodelDescriptorsByAasRequest(AasId.EncodeBase64Url(), null, null); - _aasRepositoryService.GetSubmodelRefByIdAsync(AasId, null, null, Arg.Any()) - .Returns(CreateSubmodelRefWithIds(SubmodelId1, SubmodelId2)); - _submodelDescriptorService.GetSubmodelDescriptorByIdAsync(SubmodelId1, Arg.Any()) - .Returns(new SubmodelDescriptor { Id = SubmodelId1 }); - _submodelDescriptorService.GetSubmodelDescriptorByIdAsync(SubmodelId2, Arg.Any()) - .Returns(new SubmodelDescriptor { Id = SubmodelId2 }); + _shellDescriptorService.GetAllSubmodelDescriptorsByAasIdAsync(AasId, null, null, Arg.Any()) + .Returns(new SubmodelDescriptors + { + PagingMetaData = new PagingMetaData(), + Result = + [ + new SubmodelDescriptor { Id = "SubmodelId1" }, + new SubmodelDescriptor { Id = "SubmodelId2" } + ] + }); var result = await _sut.GetAllSubmodelDescriptorsByAasId(request, CancellationToken.None); @@ -186,24 +186,10 @@ public async Task GetAllSubmodelDescriptorsByAasId_AasNotFound_PropagatesExcepti { const string AasId = "AasId"; var request = new GetSubmodelDescriptorsByAasRequest(AasId.EncodeBase64Url(), null, null); - _aasRepositoryService.GetSubmodelRefByIdAsync(AasId, null, null, Arg.Any()) + _shellDescriptorService.GetAllSubmodelDescriptorsByAasIdAsync(AasId, null, null, Arg.Any()) .ThrowsAsync(new TemplateNotFoundException()); await Assert.ThrowsAsync( () => _sut.GetAllSubmodelDescriptorsByAasId(request, CancellationToken.None)); } - - private static SubmodelRef CreateSubmodelRefWithId(string submodelId) - => new() - { - PagingMetaData = new PagingMetaData(), - Result = [new Reference(ReferenceTypes.ModelReference, [new Key(KeyTypes.Submodel, submodelId)], null)] - }; - - private static SubmodelRef CreateSubmodelRefWithIds(params string[] submodelIds) - => new() - { - PagingMetaData = new PagingMetaData(), - Result = submodelIds.Select(id => (IReference)new Reference(ReferenceTypes.ModelReference, [new Key(KeyTypes.Submodel, id)], null)).ToList() - }; } diff --git a/source/AAS.TwinEngine.DataEngine.UnitTests/Api/AasRepository/AasRepositoryControllerTests.cs b/source/AAS.TwinEngine.DataEngine.UnitTests/Api/AasRepository/AasRepositoryControllerTests.cs index 54d5ce99..1d0f8677 100644 --- a/source/AAS.TwinEngine.DataEngine.UnitTests/Api/AasRepository/AasRepositoryControllerTests.cs +++ b/source/AAS.TwinEngine.DataEngine.UnitTests/Api/AasRepository/AasRepositoryControllerTests.cs @@ -286,7 +286,7 @@ public async Task GetSubmodelByAasIdAsync_ReturnsOkWithJson() _handler.GetSubmodelByAasIdAsync(Arg.Any(), Arg.Any()) .Returns(submodel); - var result = await _sut.GetSubmodelByAasIdAsync(encodedAasId, encodedSubmodelId, null, null, CancellationToken.None); + var result = await _sut.GetSubmodelByAasIdAsync(encodedAasId, encodedSubmodelId, CancellationToken.None); var okResult = Assert.IsType(result.Result); Assert.IsType(okResult.Value); @@ -305,7 +305,7 @@ public async Task GetAllSubmodelElementsByAasIdAsync_ReturnsOk() _handler.GetAllSubmodelElementsByAasIdAsync(Arg.Any(), Arg.Any()) .Returns(expected); - var result = await _sut.GetAllSubmodelElementsByAasIdAsync(encodedAasId, encodedSubmodelId, null, null, null, null, CancellationToken.None); + var result = await _sut.GetAllSubmodelElementsByAasIdAsync(encodedAasId, encodedSubmodelId, null, null, CancellationToken.None); var okResult = Assert.IsType(result.Result); Assert.IsType(okResult.Value); diff --git a/source/AAS.TwinEngine.DataEngine.UnitTests/Api/AasRepository/Handler/AasRepositoryHandlerTests.cs b/source/AAS.TwinEngine.DataEngine.UnitTests/Api/AasRepository/Handler/AasRepositoryHandlerTests.cs index 78d27c5f..b2c7d810 100644 --- a/source/AAS.TwinEngine.DataEngine.UnitTests/Api/AasRepository/Handler/AasRepositoryHandlerTests.cs +++ b/source/AAS.TwinEngine.DataEngine.UnitTests/Api/AasRepository/Handler/AasRepositoryHandlerTests.cs @@ -2,10 +2,10 @@ using AAS.TwinEngine.DataEngine.Api.AasRepository.Requests; using AAS.TwinEngine.DataEngine.Api.AasRepository.Responses; using AAS.TwinEngine.DataEngine.Api.Shared; +using AAS.TwinEngine.DataEngine.Api.SubmodelRepository.Requests; using AAS.TwinEngine.DataEngine.ApplicationLogic.Exceptions.Application; using AAS.TwinEngine.DataEngine.ApplicationLogic.Extensions; using AAS.TwinEngine.DataEngine.ApplicationLogic.Services.AasRepository; -using AAS.TwinEngine.DataEngine.ApplicationLogic.Services.SubmodelRepository; using AAS.TwinEngine.DataEngine.DomainModel.AasRepository; using AAS.TwinEngine.DataEngine.DomainModel.Shared; using AAS.TwinEngine.DataEngine.DomainModel.SubmodelRepository; @@ -22,11 +22,10 @@ namespace AAS.TwinEngine.DataEngine.UnitTests.Api.AasRepository.Handler; public class AasRepositoryHandlerTests { private readonly IAasRepositoryService _aasRepositoryService = Substitute.For(); - private readonly ISubmodelRepositoryService _submodelRepositoryService = Substitute.For(); private readonly ILogger _logger = Substitute.For>(); private readonly AasRepositoryHandler _sut; - public AasRepositoryHandlerTests() => _sut = new AasRepositoryHandler(_logger, _aasRepositoryService, _submodelRepositoryService); + public AasRepositoryHandlerTests() => _sut = new AasRepositoryHandler(_logger, _aasRepositoryService); [Fact] public async Task GetShellsByAssetIdsAsync_WithNullAssetIds_ReturnsAllShells() @@ -272,18 +271,24 @@ public async Task GetSubmodelByAasIdAsync_ReturnsSubmodel_WhenSubmodelBelongsToA { const string AasId = "AasId"; const string SubmodelId = "SubmodelId"; - var request = new GetSubmodelByAasRequest(AasId.EncodeBase64Url(), SubmodelId.EncodeBase64Url()); + var request = new GetSubmodelByAasRequest(AasId.EncodeBase64Url(), SubmodelId.EncodeBase64Url(), Level.deep, Extent.withoutBlobValue); var expectedSubmodel = new Submodel(SubmodelId); - _aasRepositoryService.GetSubmodelRefByIdAsync(AasId, null, null, Arg.Any()) - .Returns(CreateSubmodelRefWithId(SubmodelId)); - _submodelRepositoryService.GetSubmodelByAasIdAsync(SubmodelId, null, Arg.Any()) + _aasRepositoryService.GetSubmodelByAasIdAsync( + AasId, + SubmodelId, + Arg.Is(o => o.Level == Level.deep.ToString() && o.Extent == Extent.withoutBlobValue.ToString()), + Arg.Any()) .Returns(expectedSubmodel); var result = await _sut.GetSubmodelByAasIdAsync(request, CancellationToken.None); Assert.IsType(result); - await _submodelRepositoryService.Received(1) - .GetSubmodelByAasIdAsync(SubmodelId, null, Arg.Any()); + await _aasRepositoryService.Received(1) + .GetSubmodelByAasIdAsync( + AasId, + SubmodelId, + Arg.Is(o => o.Level == Level.deep.ToString() && o.Extent == Extent.withoutBlobValue.ToString()), + Arg.Any()); } [Fact] @@ -291,9 +296,13 @@ public async Task GetSubmodelByAasIdAsync_SubmodelNotInAas_ThrowsSubmodelNotFoun { const string AasId = "AasId"; const string SubmodelId = "SubmodelId"; - var request = new GetSubmodelByAasRequest(AasId.EncodeBase64Url(), SubmodelId.EncodeBase64Url()); - _aasRepositoryService.GetSubmodelRefByIdAsync(AasId, null, null, Arg.Any()) - .Returns(CreateSubmodelRefWithId("OtherSubmodelId")); + var request = new GetSubmodelByAasRequest(AasId.EncodeBase64Url(), SubmodelId.EncodeBase64Url(), Level.deep, Extent.withoutBlobValue); + _aasRepositoryService.GetSubmodelByAasIdAsync( + AasId, + SubmodelId, + Arg.Is(o => o.Level == Level.deep.ToString() && o.Extent == Extent.withoutBlobValue.ToString()), + Arg.Any()) + .ThrowsAsync(new SubmodelNotFoundException(SubmodelId)); await Assert.ThrowsAsync( () => _sut.GetSubmodelByAasIdAsync(request, CancellationToken.None)); @@ -304,8 +313,12 @@ public async Task GetSubmodelByAasIdAsync_AasNotFound_PropagatesException() { const string AasId = "AasId"; const string SubmodelId = "SubmodelId"; - var request = new GetSubmodelByAasRequest(AasId.EncodeBase64Url(), SubmodelId.EncodeBase64Url()); - _aasRepositoryService.GetSubmodelRefByIdAsync(AasId, null, null, Arg.Any()) + var request = new GetSubmodelByAasRequest(AasId.EncodeBase64Url(), SubmodelId.EncodeBase64Url(), Level.deep, Extent.withoutBlobValue); + _aasRepositoryService.GetSubmodelByAasIdAsync( + AasId, + SubmodelId, + Arg.Is(o => o.Level == Level.deep.ToString() && o.Extent == Extent.withoutBlobValue.ToString()), + Arg.Any()) .ThrowsAsync(new TemplateNotFoundException()); await Assert.ThrowsAsync( @@ -317,17 +330,27 @@ public async Task GetAllSubmodelElementsByAasIdAsync_ReturnsList_WhenSubmodelBel { const string AasId = "AasId"; const string SubmodelId = "SubmodelId"; - var request = new GetAllSubmodelElementsByAasRequest(AasId.EncodeBase64Url(), SubmodelId.EncodeBase64Url(), null, null); - _aasRepositoryService.GetSubmodelRefByIdAsync(AasId, null, null, Arg.Any()) - .Returns(CreateSubmodelRefWithId(SubmodelId)); - _submodelRepositoryService.GetAllSubmodelElementsAsync(SubmodelId, null, null, null, Arg.Any()) + var request = new GetAllSubmodelElementsByAasRequest(AasId.EncodeBase64Url(), SubmodelId.EncodeBase64Url(), null, null, Level.deep, Extent.withoutBlobValue); + _aasRepositoryService.GetAllSubmodelElementsByAasIdAsync( + AasId, + SubmodelId, + Arg.Is(o => o.Level == Level.deep.ToString() && o.Extent == Extent.withoutBlobValue.ToString()), + null, + null, + Arg.Any()) .Returns(new SubmodelElementsPage { PagingMetaData = new PagingMetaData(), Result = [] }); var result = await _sut.GetAllSubmodelElementsByAasIdAsync(request, CancellationToken.None); Assert.NotNull(result); - await _submodelRepositoryService.Received(1) - .GetAllSubmodelElementsAsync(SubmodelId, null, null, null, Arg.Any()); + await _aasRepositoryService.Received(1) + .GetAllSubmodelElementsByAasIdAsync( + AasId, + SubmodelId, + Arg.Is(o => o.Level == Level.deep.ToString() && o.Extent == Extent.withoutBlobValue.ToString()), + null, + null, + Arg.Any()); } [Fact] @@ -335,9 +358,15 @@ public async Task GetAllSubmodelElementsByAasIdAsync_SubmodelNotInAas_ThrowsSubm { const string AasId = "AasId"; const string SubmodelId = "SubmodelId"; - var request = new GetAllSubmodelElementsByAasRequest(AasId.EncodeBase64Url(), SubmodelId.EncodeBase64Url(), null, null); - _aasRepositoryService.GetSubmodelRefByIdAsync(AasId, null, null, Arg.Any()) - .Returns(CreateSubmodelRefWithId("OtherSubmodelId")); + var request = new GetAllSubmodelElementsByAasRequest(AasId.EncodeBase64Url(), SubmodelId.EncodeBase64Url(), null, null, Level.deep, Extent.withoutBlobValue); + _aasRepositoryService.GetAllSubmodelElementsByAasIdAsync( + AasId, + SubmodelId, + Arg.Is(o => o.Level == Level.deep.ToString() && o.Extent == Extent.withoutBlobValue.ToString()), + null, + null, + Arg.Any()) + .ThrowsAsync(new SubmodelNotFoundException(SubmodelId)); await Assert.ThrowsAsync( () => _sut.GetAllSubmodelElementsByAasIdAsync(request, CancellationToken.None)); @@ -349,18 +378,21 @@ public async Task GetSubmodelElementByAasIdAsync_ReturnsElement_WhenSubmodelBelo const string AasId = "AasId"; const string SubmodelId = "SubmodelId"; const string IdShortPath = "ManufacturerName"; - var request = new GetSubmodelElementByAasRequest(AasId.EncodeBase64Url(), SubmodelId.EncodeBase64Url(), IdShortPath); + var request = new GetSubmodelElementByAasRequest( + AasId.EncodeBase64Url(), + SubmodelId.EncodeBase64Url(), + IdShortPath, + Level.deep, + Extent.withoutBlobValue); var expectedElement = new Property(idShort: IdShortPath, valueType: DataTypeDefXsd.String); - _aasRepositoryService.GetSubmodelRefByIdAsync(AasId, null, null, Arg.Any()) - .Returns(CreateSubmodelRefWithId(SubmodelId)); - _submodelRepositoryService.GetSubmodelElementAsync(SubmodelId, IdShortPath, Arg.Any()) + _aasRepositoryService.GetSubmodelElementByAasIdAsync(AasId, SubmodelId, IdShortPath, Arg.Any()) .Returns(expectedElement); var result = await _sut.GetSubmodelElementByAasIdAsync(request, CancellationToken.None); Assert.IsType(result); - await _submodelRepositoryService.Received(1) - .GetSubmodelElementAsync(SubmodelId, IdShortPath, Arg.Any()); + await _aasRepositoryService.Received(1) + .GetSubmodelElementByAasIdAsync(AasId, SubmodelId, IdShortPath, Arg.Any()); } [Fact] @@ -368,19 +400,61 @@ public async Task GetSubmodelElementByAasIdAsync_SubmodelNotInAas_ThrowsSubmodel { const string AasId = "AasId"; const string SubmodelId = "SubmodelId"; - var request = new GetSubmodelElementByAasRequest(AasId.EncodeBase64Url(), SubmodelId.EncodeBase64Url(), "SomePath"); - _aasRepositoryService.GetSubmodelRefByIdAsync(AasId, null, null, Arg.Any()) - .Returns(CreateSubmodelRefWithId("OtherSubmodelId")); + const string idShortPath = "SomePath"; + var request = new GetSubmodelElementByAasRequest( + AasId.EncodeBase64Url(), + SubmodelId.EncodeBase64Url(), + idShortPath, + Level.deep, + Extent.withoutBlobValue); + _aasRepositoryService.GetSubmodelElementByAasIdAsync(AasId, SubmodelId, idShortPath, Arg.Any()) + .ThrowsAsync(new SubmodelNotFoundException(SubmodelId)); await Assert.ThrowsAsync( () => _sut.GetSubmodelElementByAasIdAsync(request, CancellationToken.None)); } - private static SubmodelRef CreateSubmodelRefWithId(string submodelId) - => new() - { - PagingMetaData = new PagingMetaData(), - Result = [new Reference(ReferenceTypes.ModelReference, [new Key(KeyTypes.Submodel, submodelId)], null)] - }; + [Fact] + public async Task GetSubmodelByAasIdAsync_WithCoreAndWithBlobValue_PassesQueryOptionsToService() + { + const string aasId = "AasId"; + const string submodelId = "SubmodelId"; + var request = new GetSubmodelByAasRequest(aasId.EncodeBase64Url(), submodelId.EncodeBase64Url(), Level.core, Extent.withBlobValue); + var expectedSubmodel = new Submodel(submodelId); + + _aasRepositoryService.GetSubmodelByAasIdAsync( + aasId, + submodelId, + Arg.Is(o => o.Level == Level.core.ToString() && o.Extent == Extent.withBlobValue.ToString()), + Arg.Any()) + .Returns(expectedSubmodel); + + var result = await _sut.GetSubmodelByAasIdAsync(request, CancellationToken.None); + + Assert.Same(expectedSubmodel, result); + await _aasRepositoryService.Received(1).GetSubmodelByAasIdAsync( + aasId, + submodelId, + Arg.Is(o => o.Level == Level.core.ToString() && o.Extent == Extent.withBlobValue.ToString()), + Arg.Any()); + } + + [Theory] + [InlineData("")] + [InlineData(" ")] + public async Task GetSubmodelElementByAasIdAsync_InvalidIdShortPath_ThrowsInvalidUserInputException(string idShortPath) + { + const string aasId = "AasId"; + const string submodelId = "SubmodelId"; + var request = new GetSubmodelElementByAasRequest( + aasId.EncodeBase64Url(), + submodelId.EncodeBase64Url(), + idShortPath, + Level.deep, + Extent.withoutBlobValue); + + await Assert.ThrowsAsync(() => + _sut.GetSubmodelElementByAasIdAsync(request, CancellationToken.None)); + } } diff --git a/source/AAS.TwinEngine.DataEngine.UnitTests/ApplicationLogic/Services/AasRegistry/ShellDescriptorServiceTests.cs b/source/AAS.TwinEngine.DataEngine.UnitTests/ApplicationLogic/Services/AasRegistry/ShellDescriptorServiceTests.cs index 182e36b8..02c4c770 100644 --- a/source/AAS.TwinEngine.DataEngine.UnitTests/ApplicationLogic/Services/AasRegistry/ShellDescriptorServiceTests.cs +++ b/source/AAS.TwinEngine.DataEngine.UnitTests/ApplicationLogic/Services/AasRegistry/ShellDescriptorServiceTests.cs @@ -2,10 +2,14 @@ using AAS.TwinEngine.DataEngine.ApplicationLogic.Exceptions.Infrastructure; using AAS.TwinEngine.DataEngine.ApplicationLogic.Services.AasEnvironment.Providers; using AAS.TwinEngine.DataEngine.ApplicationLogic.Services.AasRegistry; +using AAS.TwinEngine.DataEngine.ApplicationLogic.Services.AasRepository; using AAS.TwinEngine.DataEngine.ApplicationLogic.Services.Plugin; +using AAS.TwinEngine.DataEngine.ApplicationLogic.Services.SubmodelRegistry; using AAS.TwinEngine.DataEngine.DomainModel.AasRegistry; +using AAS.TwinEngine.DataEngine.DomainModel.AasRepository; using AAS.TwinEngine.DataEngine.DomainModel.Plugin; using AAS.TwinEngine.DataEngine.DomainModel.Shared; +using AAS.TwinEngine.DataEngine.DomainModel.SubmodelRegistry; using AAS.TwinEngine.DataEngine.ServiceConfiguration.Config; using AasCore.Aas3_1; @@ -25,6 +29,8 @@ public class ShellDescriptorServiceTests private readonly IPluginDataHandler _pluginDataHandler = Substitute.For(); private readonly IShellDescriptorDataHandler _dataHandler = Substitute.For(); private readonly IPluginManifestConflictHandler _pluginManifestConflictHandler = Substitute.For(); + private readonly ISubmodelDescriptorService _submodelDescriptorService = Substitute.For(); + private readonly IAasRepositoryService _aasRepositoryService = Substitute.For(); private readonly ILogger _logger = Substitute.For>(); private readonly IOptions _templateManagementConfig; private readonly ShellDescriptorService _sut; @@ -36,7 +42,68 @@ public ShellDescriptorServiceTests() AasTemplateRegistry = new ServiceInstance { ConcurrentOperationsLimit = 10 } }; _templateManagementConfig = Options.Create(config); - _sut = new ShellDescriptorService(_templateProvider, _shellTemplateMappingProvider, _dataHandler, _pluginDataHandler, _pluginManifestConflictHandler, _logger, _templateManagementConfig); + _sut = new ShellDescriptorService( + _templateProvider, + _shellTemplateMappingProvider, + _dataHandler, + _pluginDataHandler, + _pluginManifestConflictHandler, + _logger, + _templateManagementConfig, + _submodelDescriptorService, + _aasRepositoryService); + } + + [Fact] + public async Task GetSubmodelDescriptorByAasIdAsync_ValidatesOwnershipAndReturnsDescriptor() + { + const string aasId = "aas-1"; + const string submodelId = "submodel-1"; + var expected = new SubmodelDescriptor { Id = submodelId }; + + _submodelDescriptorService.GetSubmodelDescriptorByIdAsync(submodelId, Arg.Any()) + .Returns(expected); + + var result = await _sut.GetSubmodelDescriptorByAasIdAsync(aasId, submodelId, CancellationToken.None); + + Assert.Same(expected, result); + await _aasRepositoryService.Received(1) + .ValidateSubmodelBelongsToAasAsync(aasId, submodelId, Arg.Any()); + await _submodelDescriptorService.Received(1) + .GetSubmodelDescriptorByIdAsync(submodelId, Arg.Any()); + } + + [Fact] + public async Task GetAllSubmodelDescriptorsByAasIdAsync_DeduplicatesSubmodelIdsAndAppliesPaging() + { + const string aasId = "aas-1"; + + _aasRepositoryService.GetSubmodelRefByIdAsync(aasId, null, null, Arg.Any()) + .Returns(new SubmodelRef + { + PagingMetaData = new PagingMetaData(), + Result = + [ + new Reference(ReferenceTypes.ModelReference, [new Key(KeyTypes.Submodel, "submodel-2")], null), + new Reference(ReferenceTypes.ModelReference, [new Key(KeyTypes.Submodel, "submodel-1")], null), + new Reference(ReferenceTypes.ModelReference, [new Key(KeyTypes.Submodel, "submodel-2")], null) + ] + }); + + _submodelDescriptorService.GetSubmodelDescriptorByIdAsync("submodel-1", Arg.Any()) + .Returns(new SubmodelDescriptor { Id = "submodel-1" }); + _submodelDescriptorService.GetSubmodelDescriptorByIdAsync("submodel-2", Arg.Any()) + .Returns(new SubmodelDescriptor { Id = "submodel-2" }); + + var result = await _sut.GetAllSubmodelDescriptorsByAasIdAsync(aasId, 1, null, CancellationToken.None); + + Assert.NotNull(result); + Assert.NotNull(result.Result); + Assert.Single(result.Result); + Assert.NotNull(result.PagingMetaData); + Assert.False(string.IsNullOrWhiteSpace(result.PagingMetaData.Cursor)); + await _submodelDescriptorService.Received(1).GetSubmodelDescriptorByIdAsync("submodel-1", Arg.Any()); + await _submodelDescriptorService.Received(1).GetSubmodelDescriptorByIdAsync("submodel-2", Arg.Any()); } [Fact] @@ -363,7 +430,7 @@ public async Task GetAllShellDescriptorsAsync_RespectsMaxConcurrency() var sut = new ShellDescriptorService( _templateProvider, _shellTemplateMappingProvider, _dataHandler, _pluginDataHandler, _pluginManifestConflictHandler, _logger, - Options.Create(config)); + Options.Create(config), _submodelDescriptorService, _aasRepositoryService); var currentConcurrency = 0; var maxObservedConcurrency = 0; diff --git a/source/AAS.TwinEngine.DataEngine.UnitTests/ApplicationLogic/Services/AasRepository/AasRepositoryServiceTests.cs b/source/AAS.TwinEngine.DataEngine.UnitTests/ApplicationLogic/Services/AasRepository/AasRepositoryServiceTests.cs index 87bff847..7acc91f0 100644 --- a/source/AAS.TwinEngine.DataEngine.UnitTests/ApplicationLogic/Services/AasRepository/AasRepositoryServiceTests.cs +++ b/source/AAS.TwinEngine.DataEngine.UnitTests/ApplicationLogic/Services/AasRepository/AasRepositoryServiceTests.cs @@ -1,12 +1,15 @@ using AAS.TwinEngine.DataEngine.ApplicationLogic.Exceptions.Application; using AAS.TwinEngine.DataEngine.ApplicationLogic.Exceptions.Infrastructure; using AAS.TwinEngine.DataEngine.ApplicationLogic.Extensions; +using AAS.TwinEngine.DataEngine.Api.SubmodelRepository.Requests; using AAS.TwinEngine.DataEngine.ApplicationLogic.Services.AasRepository; using AAS.TwinEngine.DataEngine.ApplicationLogic.Services.Plugin; +using AAS.TwinEngine.DataEngine.ApplicationLogic.Services.SubmodelRepository; using AAS.TwinEngine.DataEngine.DomainModel.AasRegistry; using AAS.TwinEngine.DataEngine.DomainModel.AasRepository; using AAS.TwinEngine.DataEngine.DomainModel.Plugin; using AAS.TwinEngine.DataEngine.DomainModel.Shared; +using AAS.TwinEngine.DataEngine.DomainModel.SubmodelRepository; using AAS.TwinEngine.DataEngine.ServiceConfiguration.Config; using AasCore.Aas3_1; @@ -24,6 +27,7 @@ public class AasRepositoryServiceTests private readonly IAasRepositoryTemplateService _templateService = Substitute.For(); private readonly IPluginDataHandler _pluginDataHandler = Substitute.For(); private readonly IPluginManifestConflictHandler _pluginManifestConflictHandler = Substitute.For(); + private readonly ISubmodelRepositoryService _submodelRepositoryService = Substitute.For(); private readonly ILogger _logger = Substitute.For>(); private readonly IOptions _templateManagementConfig = Substitute.For>(); private readonly AasRepositoryService _sut; @@ -44,9 +48,75 @@ public AasRepositoryServiceTests() _templateService, _pluginDataHandler, _pluginManifestConflictHandler, + _submodelRepositoryService, _templateManagementConfig); } + [Fact] + public async Task ValidateSubmodelBelongsToAasAsync_WithMatchingSubmodelId_DoesNotThrow() + { + _templateService.GetSubmodelRefByIdAsync(AasIdentifier, Arg.Any()) + .Returns( + [ + new Reference( + ReferenceTypes.ModelReference, + [new Key(KeyTypes.Submodel, "submodel-1")], + null) + ]); + + await _sut.ValidateSubmodelBelongsToAasAsync(AasIdentifier, "submodel-1", CancellationToken.None); + } + + [Fact] + public async Task ValidateSubmodelBelongsToAasAsync_WithoutMatchingSubmodelId_ThrowsSubmodelNotFoundException() + { + _templateService.GetSubmodelRefByIdAsync(AasIdentifier, Arg.Any()) + .Returns( + [ + new Reference( + ReferenceTypes.ModelReference, + [new Key(KeyTypes.Submodel, "other-submodel")], + null) + ]); + + await Assert.ThrowsAsync(() => + _sut.ValidateSubmodelBelongsToAasAsync(AasIdentifier, "submodel-1", CancellationToken.None)); + } + + [Fact] + public async Task GetSubmodelByAasIdAsync_WhenSubmodelBelongsToAas_ForwardsLevelAndExtentToSubmodelRepository() + { + const string submodelId = "submodel-1"; + _templateService.GetSubmodelRefByIdAsync(AasIdentifier, Arg.Any()) + .Returns( + [ + new Reference( + ReferenceTypes.ModelReference, + [new Key(KeyTypes.Submodel, submodelId)], + null) + ]); + + var expectedSubmodel = new Submodel(submodelId); + _submodelRepositoryService.GetSubmodelAsync( + submodelId, + Arg.Is(o => o.Level == Level.core.ToString() && o.Extent == Extent.withBlobValue.ToString()), + Arg.Any()) + .Returns(expectedSubmodel); + + var result = await _sut.GetSubmodelByAasIdAsync( + AasIdentifier, + submodelId, + new SubmodelQueryOptions(Level.core.ToString(), Extent.withBlobValue.ToString()), + CancellationToken.None); + + Assert.Same(expectedSubmodel, result); + await _submodelRepositoryService.Received(1) + .GetSubmodelAsync( + submodelId, + Arg.Is(o => o.Level == Level.core.ToString() && o.Extent == Extent.withBlobValue.ToString()), + Arg.Any()); + } + [Fact] public async Task GetShellByIdAsync_ShouldReturnShellWithAssetInformation() { @@ -361,7 +431,7 @@ public async Task GetShellsByFiltersAsync_ShouldBuildShellsInParallelAndSkipFail var metadataItems = new List { new() { Id = "aas-1", SpecificAssetIds = [] }, - new() { Id = "aas-2", SpecificAssetIds = [] }, + new() { Id = "aas-2", SpecificAssetIds = [] }, new() { Id = "aas-3", SpecificAssetIds = [] } }; diff --git a/source/AAS.TwinEngine.DataEngine/Api/AasRepository/Handler/AasRepositoryHandler.cs b/source/AAS.TwinEngine.DataEngine/Api/AasRepository/Handler/AasRepositoryHandler.cs index ef6b7abf..32544244 100644 --- a/source/AAS.TwinEngine.DataEngine/Api/AasRepository/Handler/AasRepositoryHandler.cs +++ b/source/AAS.TwinEngine.DataEngine/Api/AasRepository/Handler/AasRepositoryHandler.cs @@ -72,11 +72,13 @@ public Task GetSubmodelRefByIdAsync(GetSubmodelRefRequest request, public Task GetSubmodelByAasIdAsync(GetSubmodelByAasRequest request, CancellationToken cancellationToken) { + var queryOptions = request?.Level is not null || request?.Extent is not null ? new SubmodelQueryOptions(request.Level.ToString(), request.Extent.ToString()) : null; + return GetResourceByIdAsync( request?.AasIdentifier, request?.SubmodelId, "submodel By AasID", - (aasId, submodelId) => aasRepositoryService.GetSubmodelByAasIdAsync(aasId, submodelId, request.Level, request.Extent, cancellationToken)); + (aasId, submodelId) => aasRepositoryService.GetSubmodelByAasIdAsync(aasId, submodelId, queryOptions, cancellationToken)); } public Task GetAllSubmodelElementsByAasIdAsync( @@ -86,12 +88,14 @@ public Task GetAllSubmodelElementsByAasIdAsync( request?.Limit.ValidateLimit(logger); request?.Cursor?.ValidateCursor(logger); + var queryOptions = request?.Level is not null || request?.Extent is not null ? new SubmodelQueryOptions(request.Level.ToString(), request.Extent.ToString()) : null; + return GetResourceByIdAsync( request?.AasIdentifier, request?.SubmodelId, "submodel elements By AasID", async (aasId, submodelId) => (await aasRepositoryService - .GetAllSubmodelElementsByAasIdAsync(aasId, submodelId, request.Level, request.Extent, request?.Limit, request?.Cursor, cancellationToken) + .GetAllSubmodelElementsByAasIdAsync(aasId, submodelId, queryOptions, request?.Limit, request?.Cursor, cancellationToken) .ConfigureAwait(false)).ToDto()); } diff --git a/source/AAS.TwinEngine.DataEngine/ApplicationLogic/Services/AasRepository/AasRepositoryService.cs b/source/AAS.TwinEngine.DataEngine/ApplicationLogic/Services/AasRepository/AasRepositoryService.cs index 2500ce7d..16fb8313 100644 --- a/source/AAS.TwinEngine.DataEngine/ApplicationLogic/Services/AasRepository/AasRepositoryService.cs +++ b/source/AAS.TwinEngine.DataEngine/ApplicationLogic/Services/AasRepository/AasRepositoryService.cs @@ -1,5 +1,4 @@ -using AAS.TwinEngine.DataEngine.Api.SubmodelRepository.Requests; -using AAS.TwinEngine.DataEngine.ApplicationLogic.Exceptions.Application; +using AAS.TwinEngine.DataEngine.ApplicationLogic.Exceptions.Application; using AAS.TwinEngine.DataEngine.ApplicationLogic.Exceptions.Infrastructure; using AAS.TwinEngine.DataEngine.ApplicationLogic.Extensions; using AAS.TwinEngine.DataEngine.ApplicationLogic.Services.Plugin; @@ -128,42 +127,41 @@ public async Task GetSubmodelRefByIdAsync(string aasIdentifier, int }; } - public async Task GetSubmodelByAasIdAsync(string aasId, string submodelId, Level level, Extent extent, CancellationToken cancellationToken) + public async Task GetSubmodelByAasIdAsync(string aasIdentifier, string submodelIdentifier, SubmodelQueryOptions? queryOptions, CancellationToken cancellationToken) { - await ValidateSubmodelBelongsToAasAsync(aasId, submodelId, cancellationToken).ConfigureAwait(false); + await ValidateSubmodelBelongsToAasAsync(aasIdentifier, submodelIdentifier, cancellationToken).ConfigureAwait(false); - return await submodelRepositoryService.GetSubmodelAsync(submodelId, CreateQueryOptions(level, extent), cancellationToken).ConfigureAwait(false); + return await submodelRepositoryService.GetSubmodelAsync(submodelIdentifier, queryOptions, cancellationToken).ConfigureAwait(false); } - public async Task GetAllSubmodelElementsByAasIdAsync(string aasId, string submodelId, Level level, Extent extent, int? limit, string? cursor, CancellationToken cancellationToken) + public async Task GetAllSubmodelElementsByAasIdAsync(string aasIdentifier, string submodelIdentifier, SubmodelQueryOptions? queryOptions, int? limit, string? cursor, CancellationToken cancellationToken) { - await ValidateSubmodelBelongsToAasAsync(aasId, submodelId, cancellationToken).ConfigureAwait(false); + await ValidateSubmodelBelongsToAasAsync(aasIdentifier, submodelIdentifier, cancellationToken).ConfigureAwait(false); - return await submodelRepositoryService.GetAllSubmodelElementsAsync(submodelId, CreateQueryOptions(level, extent), limit, cursor, cancellationToken).ConfigureAwait(false); + return await submodelRepositoryService.GetAllSubmodelElementsAsync(submodelIdentifier, queryOptions, limit, cursor, cancellationToken).ConfigureAwait(false); } - public async Task GetSubmodelElementByAasIdAsync(string aasId, string submodelId, string idShortPath, CancellationToken cancellationToken) + public async Task GetSubmodelElementByAasIdAsync(string aasIdentifier, string submodelIdentifier, string idShortPath, CancellationToken cancellationToken) { - await ValidateSubmodelBelongsToAasAsync(aasId, submodelId, cancellationToken).ConfigureAwait(false); + await ValidateSubmodelBelongsToAasAsync(aasIdentifier, submodelIdentifier, cancellationToken).ConfigureAwait(false); - return await submodelRepositoryService.GetSubmodelElementAsync(submodelId, idShortPath, cancellationToken).ConfigureAwait(false); + return await submodelRepositoryService.GetSubmodelElementAsync(submodelIdentifier, idShortPath, cancellationToken).ConfigureAwait(false); } - public async Task ValidateSubmodelBelongsToAasAsync(string aasId, string submodelId, CancellationToken cancellationToken) + public async Task ValidateSubmodelBelongsToAasAsync(string aasIdentifier, string submodelIdentifier, CancellationToken cancellationToken) { - var submodelRefs = await GetSubmodelRefByIdAsync(aasId, null, null, cancellationToken).ConfigureAwait(false); + var submodelRefs = await GetSubmodelRefByIdAsync(aasIdentifier, null, null, cancellationToken).ConfigureAwait(false); var submodelExists = submodelRefs.Result?.SelectMany(r => r.Keys ?? []) - .Any(k => string.Equals(k.Value, submodelId, StringComparison.OrdinalIgnoreCase)) ?? false; + .Any(k => string.Equals(k.Value, submodelIdentifier, StringComparison.OrdinalIgnoreCase)) ?? false; if (!submodelExists) { - logger.LogError("Submodel {SubmodelId} not referenced by AAS {AasId}", submodelId, aasId); - throw new SubmodelNotFoundException(submodelId); + logger.LogError("Submodel {SubmodelId} not referenced by AAS {AasId}", submodelIdentifier, aasIdentifier); + throw new SubmodelNotFoundException(submodelIdentifier); } } - private static SubmodelQueryOptions CreateQueryOptions(Level level, Extent extent) => new(level.ToString(), extent.ToString()); private static IAssetInformation FillOutAssetInformation(IAssetInformation template, AssetData pluginData) { if (template is null) diff --git a/source/AAS.TwinEngine.DataEngine/ApplicationLogic/Services/AasRepository/IAasRepositoryService.cs b/source/AAS.TwinEngine.DataEngine/ApplicationLogic/Services/AasRepository/IAasRepositoryService.cs index 29abe5df..5b893f4f 100644 --- a/source/AAS.TwinEngine.DataEngine/ApplicationLogic/Services/AasRepository/IAasRepositoryService.cs +++ b/source/AAS.TwinEngine.DataEngine/ApplicationLogic/Services/AasRepository/IAasRepositoryService.cs @@ -1,7 +1,4 @@ -using AAS.TwinEngine.DataEngine.Api.SubmodelRepository.Requests; -using AAS.TwinEngine.DataEngine.DomainModel.AasRegistry; -using AAS.TwinEngine.DataEngine.DomainModel.AasRepository; -using AAS.TwinEngine.DataEngine.DomainModel.Discovery; +using AAS.TwinEngine.DataEngine.DomainModel.AasRepository; using AAS.TwinEngine.DataEngine.DomainModel.SubmodelRepository; using AasCore.Aas3_1; @@ -20,9 +17,9 @@ public interface IAasRepositoryService Task ValidateSubmodelBelongsToAasAsync(string aasIdentifier, string submodelIdentifier, CancellationToken cancellationToken); - Task GetSubmodelByAasIdAsync(string aasIdentifier, string submodelIdentifier, Level level, Extent extent, CancellationToken cancellationToken); + Task GetSubmodelByAasIdAsync(string aasIdentifier, string submodelIdentifier, SubmodelQueryOptions queryOptions, CancellationToken cancellationToken); - Task GetAllSubmodelElementsByAasIdAsync(string aasIdentifier, string submodelIdentifier, Level level, Extent extent, int? limit, string? cursor, CancellationToken cancellationToken); + Task GetAllSubmodelElementsByAasIdAsync(string aasIdentifier, string submodelIdentifier, SubmodelQueryOptions queryOptions, int? limit, string? cursor, CancellationToken cancellationToken); Task GetSubmodelElementByAasIdAsync(string aasIdentifier, string submodelIdentifier, string idShortPath, CancellationToken cancellationToken); } From 4ceccfc8cfe8f7f071020bee82d23487af0c7d28 Mon Sep 17 00:00:00 2001 From: Hardi Shah Date: Tue, 4 Aug 2026 12:14:51 +0530 Subject: [PATCH 04/25] Add new API endpoints for retrieving submodel and submodel-element descriptors --- ...del Descriptor by submodelId and aasId.bru | 21 +++++++++++++++ .../Get Submodel Descriptors by aasId.bru | 20 ++++++++++++++ .../Get Submodel by submodelId and aaslId.bru | 26 +++++++++++++++++++ ... Submodel-element by idshort and aasID.bru | 22 ++++++++++++++++ .../Get Submodel-elements by aasID.bru | 21 +++++++++++++++ 5 files changed, 110 insertions(+) create mode 100644 source/AAS.TwinEngine.Plugin.TestPlugin/Example/apiCollection/Aas Registry/Get Submodel Descriptor by submodelId and aasId.bru create mode 100644 source/AAS.TwinEngine.Plugin.TestPlugin/Example/apiCollection/Aas Registry/Get Submodel Descriptors by aasId.bru create mode 100644 source/AAS.TwinEngine.Plugin.TestPlugin/Example/apiCollection/Aas Repository/Get Submodel by submodelId and aaslId.bru create mode 100644 source/AAS.TwinEngine.Plugin.TestPlugin/Example/apiCollection/Aas Repository/Get Submodel-element by idshort and aasID.bru create mode 100644 source/AAS.TwinEngine.Plugin.TestPlugin/Example/apiCollection/Aas Repository/Get Submodel-elements by aasID.bru diff --git a/source/AAS.TwinEngine.Plugin.TestPlugin/Example/apiCollection/Aas Registry/Get Submodel Descriptor by submodelId and aasId.bru b/source/AAS.TwinEngine.Plugin.TestPlugin/Example/apiCollection/Aas Registry/Get Submodel Descriptor by submodelId and aasId.bru new file mode 100644 index 00000000..689f9d7e --- /dev/null +++ b/source/AAS.TwinEngine.Plugin.TestPlugin/Example/apiCollection/Aas Registry/Get Submodel Descriptor by submodelId and aasId.bru @@ -0,0 +1,21 @@ +meta { + name: Get Submodel Descriptor by submodelId and aasId + type: http + seq: 4 +} + +get { + url: {{DataEngineBaseUrl}}/shell-descriptors/:aasIdentifier/submodel-descriptors/:submodelIdentifier + body: none + auth: inherit +} + +params:path { + submodelIdentifier: {{submodelIdentifierHandoverDocumentation}} + aasIdentifier: {{aasIdentifier}} +} + +settings { + encodeUrl: true + timeout: 0 +} diff --git a/source/AAS.TwinEngine.Plugin.TestPlugin/Example/apiCollection/Aas Registry/Get Submodel Descriptors by aasId.bru b/source/AAS.TwinEngine.Plugin.TestPlugin/Example/apiCollection/Aas Registry/Get Submodel Descriptors by aasId.bru new file mode 100644 index 00000000..15b96ee9 --- /dev/null +++ b/source/AAS.TwinEngine.Plugin.TestPlugin/Example/apiCollection/Aas Registry/Get Submodel Descriptors by aasId.bru @@ -0,0 +1,20 @@ +meta { + name: Get Submodel Descriptors by aasId + type: http + seq: 3 +} + +get { + url: {{DataEngineBaseUrl}}/shell-descriptors/:aasIdentifier/submodel-descriptors + body: none + auth: inherit +} + +params:path { + aasIdentifier: {{aasIdentifier}} +} + +settings { + encodeUrl: true + timeout: 0 +} diff --git a/source/AAS.TwinEngine.Plugin.TestPlugin/Example/apiCollection/Aas Repository/Get Submodel by submodelId and aaslId.bru b/source/AAS.TwinEngine.Plugin.TestPlugin/Example/apiCollection/Aas Repository/Get Submodel by submodelId and aaslId.bru new file mode 100644 index 00000000..9b31d0b3 --- /dev/null +++ b/source/AAS.TwinEngine.Plugin.TestPlugin/Example/apiCollection/Aas Repository/Get Submodel by submodelId and aaslId.bru @@ -0,0 +1,26 @@ +meta { + name: Get Submodel by submodelId and aaslId + type: http + seq: 8 +} + +get { + url: {{DataEngineBaseUrl}}/shells/:aasIdentifier/submodels/:submodelIdentifier + body: none + auth: inherit +} + +params:query { + ~level: + ~extent: +} + +params:path { + submodelIdentifier: {{submodelIdentifierHandoverDocumentation}} + aasIdentifier: {{aasIdentifier}} +} + +settings { + encodeUrl: true + timeout: 0 +} diff --git a/source/AAS.TwinEngine.Plugin.TestPlugin/Example/apiCollection/Aas Repository/Get Submodel-element by idshort and aasID.bru b/source/AAS.TwinEngine.Plugin.TestPlugin/Example/apiCollection/Aas Repository/Get Submodel-element by idshort and aasID.bru new file mode 100644 index 00000000..911eea74 --- /dev/null +++ b/source/AAS.TwinEngine.Plugin.TestPlugin/Example/apiCollection/Aas Repository/Get Submodel-element by idshort and aasID.bru @@ -0,0 +1,22 @@ +meta { + name: Get Submodel-element by idshort and aasID + type: http + seq: 10 +} + +get { + url: {{DataEngineBaseUrl}}/shells/:aasIdentifier/submodels/:submodelIdentifier/submodel-elements/:idShortPath + body: none + auth: inherit +} + +params:path { + idShortPath: Documents[0].DocumentClassifications[0].ClassName + aasIdentifier: {{aasIdentifier}} + submodelIdentifier: {{submodelIdentifierHandoverDocumentation}} +} + +settings { + encodeUrl: true + timeout: 0 +} diff --git a/source/AAS.TwinEngine.Plugin.TestPlugin/Example/apiCollection/Aas Repository/Get Submodel-elements by aasID.bru b/source/AAS.TwinEngine.Plugin.TestPlugin/Example/apiCollection/Aas Repository/Get Submodel-elements by aasID.bru new file mode 100644 index 00000000..e39a639c --- /dev/null +++ b/source/AAS.TwinEngine.Plugin.TestPlugin/Example/apiCollection/Aas Repository/Get Submodel-elements by aasID.bru @@ -0,0 +1,21 @@ +meta { + name: Get Submodel-elements by aasID + type: http + seq: 9 +} + +get { + url: {{DataEngineBaseUrl}}/shells/:aasIdentifier/submodels/:submodelIdentifier/submodel-elements + body: none + auth: inherit +} + +params:path { + aasIdentifier: {{aasIdentifier}} + submodelIdentifier: {{submodelIdentifierHandoverDocumentation}} +} + +settings { + encodeUrl: true + timeout: 0 +} From 93b1c832c08d646488dbd0caa929042069ea5379 Mon Sep 17 00:00:00 2001 From: Hardi Shah Date: Tue, 4 Aug 2026 17:08:34 +0530 Subject: [PATCH 05/25] Refactor API queries in AAS Repository - Updated the submodel identifier parameter in "Get Submodel by submodelId and aaslId.bru" to use a custom submodel identifier. - Added query parameters for level and extent in "Get Submodel-element by idshort and aasID.bru". - Introduced query parameters for limit and cursor in "Get Submodel-elements by aasID.bru". --- .../Get Submodel-elements by aasID.bru | 7 +- ... Submodel-element by idshort and aasID.bru | 7 +- .../Get Submodel-elements by aasID.bru | 7 +- ... Submodel-element by idshort and aasID.bru | 5 + .../Get Submodel-elements by aasID.bru | 5 + .../Handler/ShellDescriptorHandler.cs | 6 +- .../Handler/AasRepositoryHandler.cs | 10 +- .../AasRegistry/ShellDescriptorService.cs | 2 +- ...e.Plugin.TestPlugin.PlaywrightTests.csproj | 15 + .../AasRegistry/AasRegistryTests.cs | 67 + ...llSubmodelDescriptorsByAasId_Expected.json | 122 + ...GetSubmodelDescriptorByAasId_Expected.json | 44 + .../AasRepository/AasRepositoryTests.cs | 159 +- ...etAllSubmodelElementsByAasId_Expected.json | 6935 +++++++++++++++++ ...elElementsByAasId_WithCursor_Expected.json | 39 + .../TestData/GetSubmodelByAasId_Expected.json | 1603 ++++ .../Get Submodel by submodelId and aaslId.bru | 2 +- ... Submodel-element by idshort and aasID.bru | 5 + .../Get Submodel-elements by aasID.bru | 5 + 19 files changed, 9031 insertions(+), 14 deletions(-) create mode 100644 source/AAS.TwinEngine.Plugin.TestPlugin.PlaywrightTests/AasRegistry/TestData/GetAllSubmodelDescriptorsByAasId_Expected.json create mode 100644 source/AAS.TwinEngine.Plugin.TestPlugin.PlaywrightTests/AasRegistry/TestData/GetSubmodelDescriptorByAasId_Expected.json create mode 100644 source/AAS.TwinEngine.Plugin.TestPlugin.PlaywrightTests/AasRepository/TestData/GetAllSubmodelElementsByAasId_Expected.json create mode 100644 source/AAS.TwinEngine.Plugin.TestPlugin.PlaywrightTests/AasRepository/TestData/GetAllSubmodelElementsByAasId_WithCursor_Expected.json create mode 100644 source/AAS.TwinEngine.Plugin.TestPlugin.PlaywrightTests/AasRepository/TestData/GetSubmodelByAasId_Expected.json diff --git a/example/apiCollection/Aas Repository/Product1/Get Submodel-elements by aasID.bru b/example/apiCollection/Aas Repository/Product1/Get Submodel-elements by aasID.bru index 3113da34..b11bc588 100644 --- a/example/apiCollection/Aas Repository/Product1/Get Submodel-elements by aasID.bru +++ b/example/apiCollection/Aas Repository/Product1/Get Submodel-elements by aasID.bru @@ -5,11 +5,16 @@ meta { } get { - url: {{DataEngineBaseUrl}}/shells/:aasIdentifier/submodels/:submodelIdentifier/submodel-elements + url: {{DataEngineBaseUrl}}/shells/:aasIdentifier/submodels/:submodelIdentifier/submodel-elements?limit=&cursor= body: none auth: inherit } +params:query { + limit: + cursor: +} + params:path { aasIdentifier: {{aasIdentifier-1}} submodelIdentifier: {{submodelIdentifierNameplate-1}} diff --git a/example/apiCollection/Aas Repository/Product2/Get Submodel-element by idshort and aasID.bru b/example/apiCollection/Aas Repository/Product2/Get Submodel-element by idshort and aasID.bru index 2b346646..40b561e6 100644 --- a/example/apiCollection/Aas Repository/Product2/Get Submodel-element by idshort and aasID.bru +++ b/example/apiCollection/Aas Repository/Product2/Get Submodel-element by idshort and aasID.bru @@ -5,11 +5,16 @@ meta { } get { - url: {{DataEngineBaseUrl}}/shells/:aasIdentifier/submodels/:submodelIdentifier/submodel-elements/:idShortPath + url: {{DataEngineBaseUrl}}/shells/:aasIdentifier/submodels/:submodelIdentifier/submodel-elements/:idShortPath?extent=&level= body: none auth: inherit } +params:query { + extent: + level: +} + params:path { idShortPath: ManufacturerName aasIdentifier: {{aasIdentifier-2}} diff --git a/example/apiCollection/Aas Repository/Product2/Get Submodel-elements by aasID.bru b/example/apiCollection/Aas Repository/Product2/Get Submodel-elements by aasID.bru index d1127f44..2c762290 100644 --- a/example/apiCollection/Aas Repository/Product2/Get Submodel-elements by aasID.bru +++ b/example/apiCollection/Aas Repository/Product2/Get Submodel-elements by aasID.bru @@ -5,11 +5,16 @@ meta { } get { - url: {{DataEngineBaseUrl}}/shells/:aasIdentifier/submodels/:submodelIdentifier/submodel-elements + url: {{DataEngineBaseUrl}}/shells/:aasIdentifier/submodels/:submodelIdentifier/submodel-elements?cursor=&limit= body: none auth: inherit } +params:query { + cursor: + limit: +} + params:path { aasIdentifier: {{aasIdentifier-2}} submodelIdentifier: {{submodelIdentifierNameplate-2}} diff --git a/example/apiCollection/Aas Repository/Product3/Get Submodel-element by idshort and aasID.bru b/example/apiCollection/Aas Repository/Product3/Get Submodel-element by idshort and aasID.bru index e8c4349c..393fcf7b 100644 --- a/example/apiCollection/Aas Repository/Product3/Get Submodel-element by idshort and aasID.bru +++ b/example/apiCollection/Aas Repository/Product3/Get Submodel-element by idshort and aasID.bru @@ -10,6 +10,11 @@ get { auth: inherit } +params:query { + ~level: + ~extent: +} + params:path { idShortPath: ManufacturerName aasIdentifier: {{aasIdentifier-3}} diff --git a/example/apiCollection/Aas Repository/Product3/Get Submodel-elements by aasID.bru b/example/apiCollection/Aas Repository/Product3/Get Submodel-elements by aasID.bru index e49455f8..5ef175e3 100644 --- a/example/apiCollection/Aas Repository/Product3/Get Submodel-elements by aasID.bru +++ b/example/apiCollection/Aas Repository/Product3/Get Submodel-elements by aasID.bru @@ -10,6 +10,11 @@ get { auth: inherit } +params:query { + ~limit: + ~cursor: +} + params:path { aasIdentifier: {{aasIdentifier-3}} submodelIdentifier: {{submodelIdentifierNameplate-3}} diff --git a/source/AAS.TwinEngine.DataEngine/Api/AasRegistry/Handler/ShellDescriptorHandler.cs b/source/AAS.TwinEngine.DataEngine/Api/AasRegistry/Handler/ShellDescriptorHandler.cs index eed22e06..8fb164e0 100644 --- a/source/AAS.TwinEngine.DataEngine/Api/AasRegistry/Handler/ShellDescriptorHandler.cs +++ b/source/AAS.TwinEngine.DataEngine/Api/AasRegistry/Handler/ShellDescriptorHandler.cs @@ -47,7 +47,7 @@ public Task GetAllSubmodelDescriptorsByAasId(GetSubmodel request?.AasIdentifier, "submodel descriptors by AasId", aasId => shellDescriptorService.GetAllSubmodelDescriptorsByAasIdAsync( - aasId!, + aasId, request.Limit, request.Cursor, cancellationToken), @@ -75,9 +75,9 @@ private async Task GetResourceAsync( var decodedSubmodelId = encodedSubmodelId?.DecodeBase64Url(logger); logger.LogInformation("Get {ResourceName} for AAS: {AasId}, Submodel: {SubmodelId}", resourceName, decodedAasId, decodedSubmodelId); - var result = await fetchFunc(decodedAasId!, decodedSubmodelId!).ConfigureAwait(false); + var result = await fetchFunc(decodedAasId, decodedSubmodelId).ConfigureAwait(false); ValidateResourceExists(result, resourceName); - return mapFunc(result!); + return mapFunc(result); } private async Task GetResourceAsync( diff --git a/source/AAS.TwinEngine.DataEngine/Api/AasRepository/Handler/AasRepositoryHandler.cs b/source/AAS.TwinEngine.DataEngine/Api/AasRepository/Handler/AasRepositoryHandler.cs index 32544244..db51d994 100644 --- a/source/AAS.TwinEngine.DataEngine/Api/AasRepository/Handler/AasRepositoryHandler.cs +++ b/source/AAS.TwinEngine.DataEngine/Api/AasRepository/Handler/AasRepositoryHandler.cs @@ -72,7 +72,7 @@ public Task GetSubmodelRefByIdAsync(GetSubmodelRefRequest request, public Task GetSubmodelByAasIdAsync(GetSubmodelByAasRequest request, CancellationToken cancellationToken) { - var queryOptions = request?.Level is not null || request?.Extent is not null ? new SubmodelQueryOptions(request.Level.ToString(), request.Extent.ToString()) : null; + var queryOptions = new SubmodelQueryOptions(request.Level.ToString(), request.Extent.ToString()); return GetResourceByIdAsync( request?.AasIdentifier, @@ -88,7 +88,7 @@ public Task GetAllSubmodelElementsByAasIdAsync( request?.Limit.ValidateLimit(logger); request?.Cursor?.ValidateCursor(logger); - var queryOptions = request?.Level is not null || request?.Extent is not null ? new SubmodelQueryOptions(request.Level.ToString(), request.Extent.ToString()) : null; + var queryOptions = new SubmodelQueryOptions(request.Level.ToString(), request.Extent.ToString()); return GetResourceByIdAsync( request?.AasIdentifier, @@ -144,12 +144,12 @@ private async Task GetResourceByIdAsync( logger.LogInformation("Start executing get request for {ResourceName}. AAS: {AasId}, Submodel: {SubmodelId}", resourceName, decodedAasId, decodedSubmodelId); - var result = await fetchFunc(decodedAasId!, decodedSubmodelId!) + var result = await fetchFunc(decodedAasId, decodedSubmodelId) .ConfigureAwait(false); - ValidateResourceExists(result, resourceName, decodedSubmodelId!); + ValidateResourceExists(result, resourceName, decodedSubmodelId); - return result!; + return result; } private void ValidateResourceExists(T? result, string resourceName, string decodedId) diff --git a/source/AAS.TwinEngine.DataEngine/ApplicationLogic/Services/AasRegistry/ShellDescriptorService.cs b/source/AAS.TwinEngine.DataEngine/ApplicationLogic/Services/AasRegistry/ShellDescriptorService.cs index e5a8d214..d4712f73 100644 --- a/source/AAS.TwinEngine.DataEngine/ApplicationLogic/Services/AasRegistry/ShellDescriptorService.cs +++ b/source/AAS.TwinEngine.DataEngine/ApplicationLogic/Services/AasRegistry/ShellDescriptorService.cs @@ -123,7 +123,7 @@ public class ShellDescriptorService( .OfType() .ToList(); - var (items, pagingMetaData) = PagingExtensions.GetPagedResult(descriptors, descriptor => descriptor.Id!, limit, cursor); + var (items, pagingMetaData) = PagingExtensions.GetPagedResult(descriptors, descriptor => descriptor.Id, limit, cursor); return new SubmodelDescriptors { diff --git a/source/AAS.TwinEngine.Plugin.TestPlugin.PlaywrightTests/AAS.TwinEngine.Plugin.TestPlugin.PlaywrightTests.csproj b/source/AAS.TwinEngine.Plugin.TestPlugin.PlaywrightTests/AAS.TwinEngine.Plugin.TestPlugin.PlaywrightTests.csproj index 1dfbcd55..4fa332f0 100644 --- a/source/AAS.TwinEngine.Plugin.TestPlugin.PlaywrightTests/AAS.TwinEngine.Plugin.TestPlugin.PlaywrightTests.csproj +++ b/source/AAS.TwinEngine.Plugin.TestPlugin.PlaywrightTests/AAS.TwinEngine.Plugin.TestPlugin.PlaywrightTests.csproj @@ -33,6 +33,12 @@ Always + + Always + + + Always + Always @@ -63,6 +69,15 @@ Always + + Always + + + Always + + + Always + Always diff --git a/source/AAS.TwinEngine.Plugin.TestPlugin.PlaywrightTests/AasRegistry/AasRegistryTests.cs b/source/AAS.TwinEngine.Plugin.TestPlugin.PlaywrightTests/AasRegistry/AasRegistryTests.cs index eab3827c..46fb0114 100644 --- a/source/AAS.TwinEngine.Plugin.TestPlugin.PlaywrightTests/AasRegistry/AasRegistryTests.cs +++ b/source/AAS.TwinEngine.Plugin.TestPlugin.PlaywrightTests/AasRegistry/AasRegistryTests.cs @@ -84,4 +84,71 @@ public async Task GetShellDescriptorById_ShouldReturnSuccess_ContentAsExpected() await CompareJsonAsync(actualDoc, Path.Combine(Directory.GetCurrentDirectory(), "AasRegistry", "TestData", "GetShellDescriptorById_Expected.json")); } + + [Fact] + public async Task GetAllSubmodelDescriptorsByAasId_ShouldReturnSuccess_ContentAsExpected() + { + // Arrange + var url = $"/shell-descriptors/{AasIdentifier}/submodel-descriptors"; + + // Act + var response = await ApiContext.GetAsync(url); + + // Assert + AssertSuccessResponse(response); + + var content = await response.TextAsync(); + Assert.False(string.IsNullOrEmpty(content)); + + var actualDoc = JsonDocument.Parse(content); + Assert.NotNull(actualDoc); + + await CompareJsonAsync(actualDoc, Path.Combine(Directory.GetCurrentDirectory(), "AasRegistry", "TestData", "GetAllSubmodelDescriptorsByAasId_Expected.json")); + } + + [Fact] + public async Task GetAllSubmodelDescriptorsByAasId_WithPagination() + { + // Arrange + var urlLimit2 = $"/shell-descriptors/{AasIdentifier}/submodel-descriptors?limit=2"; + var urlLimit3 = $"/shell-descriptors/{AasIdentifier}/submodel-descriptors?limit=3"; + + // Act + var responseLimit2 = await ApiContext.GetAsync(urlLimit2); + var responseLimit3 = await ApiContext.GetAsync(urlLimit3); + + // Assert + AssertSuccessResponse(responseLimit2); + AssertSuccessResponse(responseLimit3); + + var jsonLimit2 = JsonDocument.Parse(await responseLimit2.TextAsync()); + var jsonLimit3 = JsonDocument.Parse(await responseLimit3.TextAsync()); + + var resultLimit2 = jsonLimit2.RootElement.GetProperty("result"); + var resultLimit3 = jsonLimit3.RootElement.GetProperty("result"); + + Assert.Equal(resultLimit2.GetArrayLength() + 1, resultLimit3.GetArrayLength()); + } + + [Fact] + public async Task GetSubmodelDescriptorByAasId_ShouldReturnSuccess_ContentAsExpected() + { + // Arrange + var url = + $"/shell-descriptors/{AasIdentifier}/submodel-descriptors/{SubmodelIdentifierHandoverDocumentation}"; + + // Act + var response = await ApiContext.GetAsync(url); + + // Assert + AssertSuccessResponse(response); + + var content = await response.TextAsync(); + Assert.False(string.IsNullOrEmpty(content)); + + var actualDoc = JsonDocument.Parse(content); + Assert.NotNull(actualDoc); + + await CompareJsonAsync(actualDoc, Path.Combine(Directory.GetCurrentDirectory(), "AasRegistry", "TestData", "GetSubmodelDescriptorByAasId_Expected.json")); + } } diff --git a/source/AAS.TwinEngine.Plugin.TestPlugin.PlaywrightTests/AasRegistry/TestData/GetAllSubmodelDescriptorsByAasId_Expected.json b/source/AAS.TwinEngine.Plugin.TestPlugin.PlaywrightTests/AasRegistry/TestData/GetAllSubmodelDescriptorsByAasId_Expected.json new file mode 100644 index 00000000..a1f0e1a7 --- /dev/null +++ b/source/AAS.TwinEngine.Plugin.TestPlugin.PlaywrightTests/AasRegistry/TestData/GetAllSubmodelDescriptorsByAasId_Expected.json @@ -0,0 +1,122 @@ +{ + "paging_metadata": { + "cursor": null + }, + "result": [ + { + "description": [ + { + "language": "en", + "text": "The Submodel HierarchicalStructures identified by its semanticId. The Submodel idShort can be picked freely." + } + ], + "displayName": null, + "extensions": null, + "administration": null, + "idShort": "CustomSubmodel", + "id": "https://mm-software.com/submodel/000-001/CustomSubmodel", + "semanticId": { + "type": "ExternalReference", + "referredSemanticId": null, + "keys": [ + { + "type": "GlobalReference", + "value": "https://admin-shell.io/idta/CustomSubmodel/Submodel/Template/0/1" + } + ] + }, + "supplementalSemanticId": null, + "endpoints": [ + { + "interface": "SUBMODEL-3.0", + "protocolInformation": { + "href": "http://localhost:8080/submodels/aHR0cHM6Ly9tbS1zb2Z0d2FyZS5jb20vc3VibW9kZWwvMDAwLTAwMS9DdXN0b21TdWJtb2RlbA", + "endpointProtocol": "http", + "endpointProtocolVersion": null, + "subprotocol": null, + "subprotocolBody": null, + "subprotocolBodyEncoding": null, + "securityAttributes": null + } + } + ] + }, + { + "description": null, + "displayName": null, + "extensions": null, + "administration": null, + "idShort": "ContactInformations", + "id": "https://mm-software.com/submodel/000-001/ContactInformation", + "semanticId": { + "type": "ModelReference", + "referredSemanticId": null, + "keys": [ + { + "type": "Submodel", + "value": "https://admin-shell.io/zvei/nameplate/1/0/ContactInformations" + } + ] + }, + "supplementalSemanticId": null, + "endpoints": [ + { + "interface": "SUBMODEL-3.0", + "protocolInformation": { + "href": "http://localhost:8080/submodels/aHR0cHM6Ly9tbS1zb2Z0d2FyZS5jb20vc3VibW9kZWwvMDAwLTAwMS9Db250YWN0SW5mb3JtYXRpb24", + "endpointProtocol": "http", + "endpointProtocolVersion": null, + "subprotocol": null, + "subprotocolBody": null, + "subprotocolBodyEncoding": null, + "securityAttributes": null + } + } + ] + }, + { + "description": [ + { + "language": "en", + "text": "The Submodel defines a set meta data for the handover of documentation from the manufacturer to the operator for industrial equipment" + } + ], + "displayName": null, + "extensions": null, + "administration": { + "embeddedDataSpecifications": null, + "version": "2", + "revision": "0", + "creator": null, + "templateId": "https://admin-shell.io/idta-02004-2-0" + }, + "idShort": "HandoverDocumentation", + "id": "https://mm-software.com/submodel/000-001/HandoverDocumentation", + "semanticId": { + "type": "ModelReference", + "referredSemanticId": null, + "keys": [ + { + "type": "Submodel", + "value": "0173-1#01-AHF578#003" + } + ] + }, + "supplementalSemanticId": null, + "endpoints": [ + { + "interface": "SUBMODEL-3.0", + "protocolInformation": { + "href": "http://localhost:8080/submodels/aHR0cHM6Ly9tbS1zb2Z0d2FyZS5jb20vc3VibW9kZWwvMDAwLTAwMS9IYW5kb3ZlckRvY3VtZW50YXRpb24", + "endpointProtocol": "http", + "endpointProtocolVersion": null, + "subprotocol": null, + "subprotocolBody": null, + "subprotocolBodyEncoding": null, + "securityAttributes": null + } + } + ] + } + ] +} diff --git a/source/AAS.TwinEngine.Plugin.TestPlugin.PlaywrightTests/AasRegistry/TestData/GetSubmodelDescriptorByAasId_Expected.json b/source/AAS.TwinEngine.Plugin.TestPlugin.PlaywrightTests/AasRegistry/TestData/GetSubmodelDescriptorByAasId_Expected.json new file mode 100644 index 00000000..8695ba29 --- /dev/null +++ b/source/AAS.TwinEngine.Plugin.TestPlugin.PlaywrightTests/AasRegistry/TestData/GetSubmodelDescriptorByAasId_Expected.json @@ -0,0 +1,44 @@ +{ + "description": [ + { + "language": "en", + "text": "The Submodel defines a set meta data for the handover of documentation from the manufacturer to the operator for industrial equipment" + } + ], + "displayName": null, + "extensions": null, + "administration": { + "embeddedDataSpecifications": null, + "version": "2", + "revision": "0", + "creator": null, + "templateId": "https://admin-shell.io/idta-02004-2-0" + }, + "idShort": "HandoverDocumentation", + "id": "https://mm-software.com/submodel/000-001/HandoverDocumentation", + "semanticId": { + "type": "ModelReference", + "referredSemanticId": null, + "keys": [ + { + "type": "Submodel", + "value": "0173-1#01-AHF578#003" + } + ] + }, + "supplementalSemanticId": null, + "endpoints": [ + { + "interface": "SUBMODEL-3.0", + "protocolInformation": { + "href": "http://localhost:8080/submodels/aHR0cHM6Ly9tbS1zb2Z0d2FyZS5jb20vc3VibW9kZWwvMDAwLTAwMS9IYW5kb3ZlckRvY3VtZW50YXRpb24", + "endpointProtocol": "http", + "endpointProtocolVersion": null, + "subprotocol": null, + "subprotocolBody": null, + "subprotocolBodyEncoding": null, + "securityAttributes": null + } + } + ] +} diff --git a/source/AAS.TwinEngine.Plugin.TestPlugin.PlaywrightTests/AasRepository/AasRepositoryTests.cs b/source/AAS.TwinEngine.Plugin.TestPlugin.PlaywrightTests/AasRepository/AasRepositoryTests.cs index bd4b1104..c310fbb7 100644 --- a/source/AAS.TwinEngine.Plugin.TestPlugin.PlaywrightTests/AasRepository/AasRepositoryTests.cs +++ b/source/AAS.TwinEngine.Plugin.TestPlugin.PlaywrightTests/AasRepository/AasRepositoryTests.cs @@ -209,7 +209,6 @@ public async Task GetShellById_ShouldReturnSuccess_ContentAsExpected() var content = await response.TextAsync(); Assert.False(string.IsNullOrEmpty(content)); - // Verify it's valid JSON var json = JsonDocument.Parse(content); Assert.NotNull(json); @@ -256,6 +255,164 @@ public async Task GetSubmodelRefById_ShouldReturnSuccess_ContentAsExpected() await CompareJsonAsync(json, Path.Combine(Directory.GetCurrentDirectory(), "AasRepository", "TestData", "GetSubmodelRefById_Expected.json")); } + [Fact] + public async Task GetSubmodelByAasId_ShouldReturnSuccess_ContentAsExpected() + { + // Arrange + var url = $"/shells/{AasIdentifier}/submodels/{SubmodelIdentifierCustomSubmodel}"; + + // Act + var response = await ApiContext.GetAsync(url); + + // Assert + AssertSuccessResponse(response); + + var content = await response.TextAsync(); + Assert.False(string.IsNullOrEmpty(content)); + + var json = JsonDocument.Parse(content); + Assert.NotNull(json); + + await CompareJsonAsync(json, Path.Combine(Directory.GetCurrentDirectory(), "AasRepository", "TestData", "GetSubmodelByAasId_Expected.json")); + } + + [Fact] + public async Task GetSubmodelByAasId_WithLevelAndExtent_ShouldReturnSuccess() + { + // Arrange + var url = $"/shells/{AasIdentifier}/submodels/{SubmodelIdentifierCustomSubmodel}" + "?level=deep&extent=withoutBlobValue"; + + // Act + var response = await ApiContext.GetAsync(url); + + // Assert + AssertSuccessResponse(response); + + var content = await response.TextAsync(); + Assert.False(string.IsNullOrEmpty(content)); + + var json = JsonDocument.Parse(content); + Assert.NotNull(json); + + await CompareJsonAsync(json, Path.Combine(Directory.GetCurrentDirectory(), "AasRepository", "TestData", "GetSubmodelByAasId_Expected.json")); + } + + [Fact] + public async Task GetSubmodelByAasId_WithInvalidSubmodelId_ShouldReturnNotFound() + { + // Arrange + var url = $"/shells/{AasIdentifier}/submodels/invalid//submodel"; + + // Act + var response = await ApiContext.GetAsync(url); + + // Assert + Assert.Equal(404, response.Status); + } + + [Fact] + public async Task GetAllSubmodelElementsByAasId_ShouldReturnSuccess_ContentAsExpected() + { + // Arrange + var url = $"/shells/{AasIdentifier}/submodels/{SubmodelIdentifierHandoverDocumentation}/submodel-elements"; + + // Act + var response = await ApiContext.GetAsync(url); + + // Assert + AssertSuccessResponse(response); + + var content = await response.TextAsync(); + Assert.False(string.IsNullOrEmpty(content)); + + var json = JsonDocument.Parse(content); + Assert.NotNull(json); + + await CompareJsonAsync(json, Path.Combine(Directory.GetCurrentDirectory(), "AasRepository", "TestData", "GetAllSubmodelElementsByAasId_Expected.json")); + } + + [Fact] + public async Task GetAllSubmodelElementsByAasId_WithPagination() + { + // Arrange + var urlLimit2 = $"/shells/{AasIdentifier}/submodels/{SubmodelIdentifierHandoverDocumentation}/submodel-elements?limit=1"; + + var urlLimit3 = $"/shells/{AasIdentifier}/submodels/{SubmodelIdentifierHandoverDocumentation}/submodel-elements?limit=2"; + + // Act + var responseLimit2 = await ApiContext.GetAsync(urlLimit2); + var responseLimit3 = await ApiContext.GetAsync(urlLimit3); + + // Assert + AssertSuccessResponse(responseLimit2); + AssertSuccessResponse(responseLimit3); + + var jsonLimit2 = JsonDocument.Parse(await responseLimit2.TextAsync()); + var jsonLimit3 = JsonDocument.Parse(await responseLimit3.TextAsync()); + + var resultLimit2 = jsonLimit2.RootElement.GetProperty("result"); + var resultLimit3 = jsonLimit3.RootElement.GetProperty("result"); + + Assert.Equal(resultLimit2.GetArrayLength() + 1, resultLimit3.GetArrayLength()); + } + + [Fact] + public async Task GetAllSubmodelElementsByAasId_WithCursor_ShouldReturnNextPage() + { + // Arrange + var firstPageUrl = $"/shells/{AasIdentifier}/submodels/{SubmodelIdentifierHandoverDocumentation}/submodel-elements?limit=1"; + + // Act + var firstResponse = await ApiContext.GetAsync(firstPageUrl); + + // Assert first page + AssertSuccessResponse(firstResponse); + + var firstRoot = await ParseResponseRootAsync(firstResponse); + var firstResult = firstRoot.GetProperty("result"); + + Assert.Single(firstResult.EnumerateArray()); + + var cursor = firstRoot.GetProperty("paging_metadata").GetProperty("cursor").GetString(); + + Assert.False(string.IsNullOrWhiteSpace(cursor)); + + var firstIdShort = firstResult[0].GetProperty("idShort").GetString(); + + // Act second page + var secondResponse = await ApiContext.GetAsync($"/shells/{AasIdentifier}/submodels/{SubmodelIdentifierHandoverDocumentation}/submodel-elements?limit=1&cursor={cursor}"); + + // Assert second page + AssertSuccessResponse(secondResponse); + + var secondRoot = await ParseResponseRootAsync(secondResponse); + var secondResult = secondRoot.GetProperty("result"); + + Assert.Single(secondResult.EnumerateArray()); + + var secondIdShort = secondResult[0].GetProperty("idShort").GetString(); + + Assert.NotEqual(firstIdShort, secondIdShort); + + var json = JsonDocument.Parse(secondRoot.GetRawText()); + + await CompareJsonAsync(json, Path.Combine(Directory.GetCurrentDirectory(), "AasRepository", "TestData", "GetAllSubmodelElementsByAasId_WithCursor_Expected.json")); + } + + [Fact] + public async Task GetAllSubmodelElementsByAasId_WithInvalidLimit_ShouldReturnBadRequest() + { + // Arrange + var url = + $"/shells/{AasIdentifier}/submodels/{SubmodelIdentifierHandoverDocumentation}/submodel-elements?limit=0"; + + // Act + var response = await ApiContext.GetAsync(url); + + // Assert + Assert.Equal(400, response.Status); + } + private static async Task ParseResponseRootAsync(Microsoft.Playwright.IAPIResponse response) { var content = await response.TextAsync(); diff --git a/source/AAS.TwinEngine.Plugin.TestPlugin.PlaywrightTests/AasRepository/TestData/GetAllSubmodelElementsByAasId_Expected.json b/source/AAS.TwinEngine.Plugin.TestPlugin.PlaywrightTests/AasRepository/TestData/GetAllSubmodelElementsByAasId_Expected.json new file mode 100644 index 00000000..6f50545b --- /dev/null +++ b/source/AAS.TwinEngine.Plugin.TestPlugin.PlaywrightTests/AasRepository/TestData/GetAllSubmodelElementsByAasId_Expected.json @@ -0,0 +1,6935 @@ +{ + "paging_metadata": { + "cursor": null + }, + "result": [ + { + "idShort": "Documents", + "displayName": [ + { + "language": "en", + "text": "Documents (handover documentation)" + }, + { + "language": "de", + "text": "Dokumente (Übergabedokumentation)" + } + ], + "semanticId": { + "type": "ExternalReference", + "keys": [ + { + "type": "GlobalReference", + "value": "0173-1#02-ABI500#003" + } + ] + }, + "supplementalSemanticIds": [ + { + "type": "ExternalReference", + "keys": [ + { + "type": "GlobalReference", + "value": "https://api.eclass-cdp.com/0173-1-02-ABI500-003" + } + ] + } + ], + "qualifiers": [ + { + "semanticId": { + "type": "ExternalReference", + "keys": [ + { + "type": "GlobalReference", + "value": "https://admin-shell.io/SubmodelTemplates/Cardinality/1/0" + } + ] + }, + "kind": "ConceptQualifier", + "type": "SMT/Cardinality", + "valueType": "xs:string", + "value": "One" + } + ], + "orderRelevant": false, + "semanticIdListElement": { + "type": "ExternalReference", + "keys": [ + { + "type": "GlobalReference", + "value": "0173-1#02-ABI500#003/0173-1#01-AHF579#003" + } + ] + }, + "typeValueListElement": "SubmodelElementCollection", + "value": [ + { + "description": [ + { + "language": "en", + "text": "This SubmodelElementCollection holds the information for a VDI 2770 Document entity" + } + ], + "semanticId": { + "type": "ExternalReference", + "keys": [ + { + "type": "GlobalReference", + "value": "0173-1#02-ABI500#003/0173-1#01-AHF579#003" + } + ] + }, + "supplementalSemanticIds": [ + { + "type": "ExternalReference", + "keys": [ + { + "type": "GlobalReference", + "value": "0173-1#02-ABI500#003~0/0173-1#01-AHF579#003" + }, + { + "type": "GlobalReference", + "value": "https://api.eclass-cdp.com/0173-1-02-ABI500-003/0173-1-01-AHF579-003" + } + ] + } + ], + "qualifiers": [ + { + "semanticId": { + "type": "ExternalReference", + "keys": [ + { + "type": "GlobalReference", + "value": "https://admin-shell.io/SubmodelTemplates/Cardinality/1/0" + } + ] + }, + "kind": "ConceptQualifier", + "type": "SMT/Cardinality", + "valueType": "xs:string", + "value": "OneToMany" + } + ], + "value": [ + { + "idShort": "DocumentIds", + "displayName": [ + { + "language": "en", + "text": "Document identifyers" + }, + { + "language": "de", + "text": "Dokumentidentifikatoren" + } + ], + "semanticId": { + "type": "ExternalReference", + "keys": [ + { + "type": "GlobalReference", + "value": "0173-1#02-ABI501#003" + } + ] + }, + "supplementalSemanticIds": [ + { + "type": "ExternalReference", + "keys": [ + { + "type": "GlobalReference", + "value": "https://api.eclass-cdp.com/0173-1-02-ABI501-003" + } + ] + } + ], + "qualifiers": [ + { + "semanticId": { + "type": "ExternalReference", + "keys": [ + { + "type": "GlobalReference", + "value": "https://admin-shell.io/SubmodelTemplates/Cardinality/1/0" + } + ] + }, + "kind": "ConceptQualifier", + "type": "SMT/Cardinality", + "valueType": "xs:string", + "value": "One" + } + ], + "orderRelevant": false, + "semanticIdListElement": { + "type": "ExternalReference", + "keys": [ + { + "type": "GlobalReference", + "value": "0173-1#02-ABI501#003/0173-1#01-AHF580#003" + } + ] + }, + "typeValueListElement": "SubmodelElementCollection", + "value": [ + { + "displayName": [ + { + "language": "en", + "text": "Document identificator" + }, + { + "language": "de", + "text": "Dokumentidentifikator" + } + ], + "description": [ + { + "language": "en", + "text": "This SubmodelElementCollection holds the information for a VDI 2770 Document entity" + } + ], + "semanticId": { + "type": "ExternalReference", + "keys": [ + { + "type": "GlobalReference", + "value": "0173-1#02-ABI501#003/0173-1#01-AHF580#003" + } + ] + }, + "supplementalSemanticIds": [ + { + "type": "ExternalReference", + "keys": [ + { + "type": "GlobalReference", + "value": "0173-1#02-ABI501#003~0/0173-1#01-AHF580#003" + }, + { + "type": "GlobalReference", + "value": "https://api.eclass-cdp.com/0173-1-02-ABI501-003/0173-1-01-AHF580-003" + } + ] + } + ], + "qualifiers": [ + { + "semanticId": { + "type": "ExternalReference", + "keys": [ + { + "type": "GlobalReference", + "value": "https://admin-shell.io/SubmodelTemplates/Cardinality/1/0" + } + ] + }, + "kind": "ConceptQualifier", + "type": "SMT/Cardinality", + "valueType": "xs:string", + "value": "OneToMany" + } + ], + "value": [ + { + "category": "PARAMETER", + "idShort": "DocumentDomainId", + "displayName": [ + { + "language": "en", + "text": "document domain identificator" + }, + { + "language": "de", + "text": "Document Domain Identifikator" + } + ], + "semanticId": { + "type": "ExternalReference", + "keys": [ + { + "type": "GlobalReference", + "value": "0173-1#02-ABH994#003" + } + ] + }, + "supplementalSemanticIds": [ + { + "type": "ExternalReference", + "keys": [ + { + "type": "GlobalReference", + "value": "https://api.eclass-cdp.com/0173-1-02-ABH994-003" + } + ] + } + ], + "qualifiers": [ + { + "semanticId": { + "type": "ExternalReference", + "keys": [ + { + "type": "GlobalReference", + "value": "https://admin-shell.io/SubmodelTemplates/Cardinality/1/0" + } + ] + }, + "kind": "ConceptQualifier", + "type": "SMT/Cardinality", + "valueType": "xs:string", + "value": "One" + }, + { + "semanticId": { + "type": "ExternalReference", + "keys": [ + { + "type": "GlobalReference", + "value": "https://admin-shell.io/SubmodelTemplates/ExampleValue/1/0" + } + ] + }, + "kind": "ConceptQualifier", + "type": "ExampleValue", + "valueType": "xs:string", + "value": "https://domain.com/..." + } + ], + "valueType": "xs:string", + "value": "a3f1c2b4-9d81-4e5a-b6f2-01ac9e11d001", + "modelType": "Property" + }, + { + "category": "PARAMETER", + "idShort": "DocumentIdentifier", + "displayName": [ + { + "language": "en", + "text": "Document Identifyer" + }, + { + "language": "de", + "text": "Dokumentennummer" + } + ], + "semanticId": { + "type": "ExternalReference", + "keys": [ + { + "type": "GlobalReference", + "value": "0173-1#02-AAO099#004" + } + ] + }, + "supplementalSemanticIds": [ + { + "type": "ExternalReference", + "keys": [ + { + "type": "GlobalReference", + "value": "https://api.eclass-cdp.com/0173-1-02-AAO099-004" + } + ] + } + ], + "qualifiers": [ + { + "semanticId": { + "type": "ExternalReference", + "keys": [ + { + "type": "GlobalReference", + "value": "https://admin-shell.io/SubmodelTemplates/Cardinality/1/0" + } + ] + }, + "kind": "ConceptQualifier", + "type": "SMT/Cardinality", + "valueType": "xs:string", + "value": "One" + }, + { + "semanticId": { + "type": "ExternalReference", + "keys": [ + { + "type": "GlobalReference", + "value": "https://admin-shell.io/SubmodelTemplates/ExampleValue/1/0" + } + ] + }, + "kind": "ConceptQualifier", + "type": "ExampleValue", + "valueType": "xs:string", + "value": "XF90-884" + } + ], + "valueType": "xs:string", + "value": "DOC2025A001", + "modelType": "Property" + }, + { + "category": "PARAMETER", + "idShort": "DocumentIsPrimary", + "displayName": [ + { + "language": "en", + "text": "Document is primary" + }, + { + "language": "de", + "text": "Dokument ist primär" + } + ], + "semanticId": { + "type": "ExternalReference", + "keys": [ + { + "type": "GlobalReference", + "value": "0173-1#02-ABH995#003" + } + ] + }, + "supplementalSemanticIds": [ + { + "type": "ExternalReference", + "keys": [ + { + "type": "GlobalReference", + "value": "https://api.eclass-cdp.com/0173-1-02-ABH995-003" + } + ] + } + ], + "qualifiers": [ + { + "semanticId": { + "type": "ExternalReference", + "keys": [ + { + "type": "GlobalReference", + "value": "https://admin-shell.io/SubmodelTemplates/Cardinality/1/0" + } + ] + }, + "kind": "ConceptQualifier", + "type": "SMT/Cardinality", + "valueType": "xs:string", + "value": "ZeroToOne" + }, + { + "semanticId": { + "type": "ExternalReference", + "keys": [ + { + "type": "GlobalReference", + "value": "https://admin-shell.io/SubmodelTemplates/ExampleValue/1/0" + } + ] + }, + "kind": "ConceptQualifier", + "type": "ExampleValue", + "valueType": "xs:string", + "value": "true" + } + ], + "valueType": "xs:boolean", + "value": "True", + "modelType": "Property" + } + ], + "modelType": "SubmodelElementCollection" + }, + { + "displayName": [ + { + "language": "en", + "text": "Document identificator" + }, + { + "language": "de", + "text": "Dokumentidentifikator" + } + ], + "description": [ + { + "language": "en", + "text": "This SubmodelElementCollection holds the information for a VDI 2770 Document entity" + } + ], + "semanticId": { + "type": "ExternalReference", + "keys": [ + { + "type": "GlobalReference", + "value": "0173-1#02-ABI501#003/0173-1#01-AHF580#003" + } + ] + }, + "supplementalSemanticIds": [ + { + "type": "ExternalReference", + "keys": [ + { + "type": "GlobalReference", + "value": "0173-1#02-ABI501#003~0/0173-1#01-AHF580#003" + }, + { + "type": "GlobalReference", + "value": "https://api.eclass-cdp.com/0173-1-02-ABI501-003/0173-1-01-AHF580-003" + } + ] + } + ], + "qualifiers": [ + { + "semanticId": { + "type": "ExternalReference", + "keys": [ + { + "type": "GlobalReference", + "value": "https://admin-shell.io/SubmodelTemplates/Cardinality/1/0" + } + ] + }, + "kind": "ConceptQualifier", + "type": "SMT/Cardinality", + "valueType": "xs:string", + "value": "OneToMany" + } + ], + "value": [ + { + "category": "PARAMETER", + "idShort": "DocumentDomainId", + "displayName": [ + { + "language": "en", + "text": "document domain identificator" + }, + { + "language": "de", + "text": "Document Domain Identifikator" + } + ], + "semanticId": { + "type": "ExternalReference", + "keys": [ + { + "type": "GlobalReference", + "value": "0173-1#02-ABH994#003" + } + ] + }, + "supplementalSemanticIds": [ + { + "type": "ExternalReference", + "keys": [ + { + "type": "GlobalReference", + "value": "https://api.eclass-cdp.com/0173-1-02-ABH994-003" + } + ] + } + ], + "qualifiers": [ + { + "semanticId": { + "type": "ExternalReference", + "keys": [ + { + "type": "GlobalReference", + "value": "https://admin-shell.io/SubmodelTemplates/Cardinality/1/0" + } + ] + }, + "kind": "ConceptQualifier", + "type": "SMT/Cardinality", + "valueType": "xs:string", + "value": "One" + }, + { + "semanticId": { + "type": "ExternalReference", + "keys": [ + { + "type": "GlobalReference", + "value": "https://admin-shell.io/SubmodelTemplates/ExampleValue/1/0" + } + ] + }, + "kind": "ConceptQualifier", + "type": "ExampleValue", + "valueType": "xs:string", + "value": "https://domain.com/..." + } + ], + "valueType": "xs:string", + "value": "b7e92d10-3c45-4a8f-9f21-02bc9e11d002", + "modelType": "Property" + }, + { + "category": "PARAMETER", + "idShort": "DocumentIdentifier", + "displayName": [ + { + "language": "en", + "text": "Document Identifyer" + }, + { + "language": "de", + "text": "Dokumentennummer" + } + ], + "semanticId": { + "type": "ExternalReference", + "keys": [ + { + "type": "GlobalReference", + "value": "0173-1#02-AAO099#004" + } + ] + }, + "supplementalSemanticIds": [ + { + "type": "ExternalReference", + "keys": [ + { + "type": "GlobalReference", + "value": "https://api.eclass-cdp.com/0173-1-02-AAO099-004" + } + ] + } + ], + "qualifiers": [ + { + "semanticId": { + "type": "ExternalReference", + "keys": [ + { + "type": "GlobalReference", + "value": "https://admin-shell.io/SubmodelTemplates/Cardinality/1/0" + } + ] + }, + "kind": "ConceptQualifier", + "type": "SMT/Cardinality", + "valueType": "xs:string", + "value": "One" + }, + { + "semanticId": { + "type": "ExternalReference", + "keys": [ + { + "type": "GlobalReference", + "value": "https://admin-shell.io/SubmodelTemplates/ExampleValue/1/0" + } + ] + }, + "kind": "ConceptQualifier", + "type": "ExampleValue", + "valueType": "xs:string", + "value": "XF90-884" + } + ], + "valueType": "xs:string", + "value": "CERTX94B21", + "modelType": "Property" + }, + { + "category": "PARAMETER", + "idShort": "DocumentIsPrimary", + "displayName": [ + { + "language": "en", + "text": "Document is primary" + }, + { + "language": "de", + "text": "Dokument ist primär" + } + ], + "semanticId": { + "type": "ExternalReference", + "keys": [ + { + "type": "GlobalReference", + "value": "0173-1#02-ABH995#003" + } + ] + }, + "supplementalSemanticIds": [ + { + "type": "ExternalReference", + "keys": [ + { + "type": "GlobalReference", + "value": "https://api.eclass-cdp.com/0173-1-02-ABH995-003" + } + ] + } + ], + "qualifiers": [ + { + "semanticId": { + "type": "ExternalReference", + "keys": [ + { + "type": "GlobalReference", + "value": "https://admin-shell.io/SubmodelTemplates/Cardinality/1/0" + } + ] + }, + "kind": "ConceptQualifier", + "type": "SMT/Cardinality", + "valueType": "xs:string", + "value": "ZeroToOne" + }, + { + "semanticId": { + "type": "ExternalReference", + "keys": [ + { + "type": "GlobalReference", + "value": "https://admin-shell.io/SubmodelTemplates/ExampleValue/1/0" + } + ] + }, + "kind": "ConceptQualifier", + "type": "ExampleValue", + "valueType": "xs:string", + "value": "true" + } + ], + "valueType": "xs:boolean", + "value": "False", + "modelType": "Property" + } + ], + "modelType": "SubmodelElementCollection" + } + ], + "modelType": "SubmodelElementList" + }, + { + "idShort": "DocumentClassifications", + "displayName": [ + { + "language": "en", + "text": "Document classifications" + }, + { + "language": "de", + "text": "Dokumentklassifikationen" + } + ], + "semanticId": { + "type": "ExternalReference", + "keys": [ + { + "type": "GlobalReference", + "value": "0173-1#02-ABI502#003" + } + ] + }, + "supplementalSemanticIds": [ + { + "type": "ExternalReference", + "keys": [ + { + "type": "GlobalReference", + "value": "https://api.eclass-cdp.com/0173-1-02-ABI502-003" + } + ] + } + ], + "qualifiers": [ + { + "semanticId": { + "type": "ExternalReference", + "keys": [ + { + "type": "GlobalReference", + "value": "https://admin-shell.io/SubmodelTemplates/Cardinality/1/0" + } + ] + }, + "kind": "ConceptQualifier", + "type": "SMT/Cardinality", + "valueType": "xs:string", + "value": "One" + } + ], + "orderRelevant": false, + "semanticIdListElement": { + "type": "ExternalReference", + "keys": [ + { + "type": "GlobalReference", + "value": "0173-1#02-ABI502#003/0173-1#01-AHF581#003" + } + ] + }, + "typeValueListElement": "SubmodelElementCollection", + "value": [ + { + "displayName": [ + { + "language": "en", + "text": "Document classification" + }, + { + "language": "de", + "text": "Dokumentklassifikation" + } + ], + "description": [ + { + "language": "en", + "text": "Set of information for describing the classification of the Document according to a ClassificationSystem" + } + ], + "semanticId": { + "type": "ExternalReference", + "keys": [ + { + "type": "GlobalReference", + "value": "0173-1#02-ABI502#003/0173-1#01-AHF581#003" + } + ] + }, + "supplementalSemanticIds": [ + { + "type": "ExternalReference", + "keys": [ + { + "type": "GlobalReference", + "value": "0173-1#02-ABI502#003~0/0173-1#01-AHF581#003" + }, + { + "type": "GlobalReference", + "value": "https://api.eclass-cdp.com/0173-1-02-ABI502-003/0173-1-01-AHF581-003" + } + ] + } + ], + "qualifiers": [ + { + "semanticId": { + "type": "ExternalReference", + "keys": [ + { + "type": "GlobalReference", + "value": "https://admin-shell.io/SubmodelTemplates/Cardinality/1/0" + } + ] + }, + "kind": "ConceptQualifier", + "type": "SMT/Cardinality", + "valueType": "xs:string", + "value": "OneToMany" + } + ], + "value": [ + { + "category": "PARAMETER", + "idShort": "ClassId", + "displayName": [ + { + "language": "en", + "text": "Class identificator" + }, + { + "language": "de", + "text": "Klassenidentifikator" + } + ], + "semanticId": { + "type": "ExternalReference", + "keys": [ + { + "type": "GlobalReference", + "value": "0173-1#02-ABH996#003" + } + ] + }, + "supplementalSemanticIds": [ + { + "type": "ExternalReference", + "keys": [ + { + "type": "GlobalReference", + "value": "https://api.eclass-cdp.com/0173-1-02-ABH996-003" + } + ] + } + ], + "qualifiers": [ + { + "semanticId": { + "type": "ExternalReference", + "keys": [ + { + "type": "GlobalReference", + "value": "https://admin-shell.io/SubmodelTemplates/Cardinality/1/0" + } + ] + }, + "kind": "ConceptQualifier", + "type": "SMT/Cardinality", + "valueType": "xs:string", + "value": "One" + }, + { + "semanticId": { + "type": "ExternalReference", + "keys": [ + { + "type": "GlobalReference", + "value": "https://admin-shell.io/SubmodelTemplates/ExampleValue/1/0" + } + ] + }, + "kind": "ConceptQualifier", + "type": "ExampleValue", + "valueType": "xs:string", + "value": "03-02" + } + ], + "valueType": "xs:string", + "value": "CLS-001", + "modelType": "Property" + }, + { + "category": "PARAMETER", + "idShort": "ClassificationSystem", + "displayName": [ + { + "language": "en", + "text": "Classification system" + }, + { + "language": "de", + "text": "Klassifizierungssystem" + } + ], + "semanticId": { + "type": "ExternalReference", + "keys": [ + { + "type": "GlobalReference", + "value": "0173-1#02-ABH997#003" + } + ] + }, + "supplementalSemanticIds": [ + { + "type": "ExternalReference", + "keys": [ + { + "type": "GlobalReference", + "value": "https://api.eclass-cdp.com/0173-1-02-ABH997-003" + } + ] + } + ], + "qualifiers": [ + { + "semanticId": { + "type": "ExternalReference", + "keys": [ + { + "type": "GlobalReference", + "value": "https://admin-shell.io/SubmodelTemplates/Cardinality/1/0" + } + ] + }, + "kind": "ConceptQualifier", + "type": "SMT/Cardinality", + "valueType": "xs:string", + "value": "One" + }, + { + "semanticId": { + "type": "ExternalReference", + "keys": [ + { + "type": "GlobalReference", + "value": "https://admin-shell.io/SubmodelTemplates/ExampleValue/1/0" + } + ] + }, + "kind": "ConceptQualifier", + "type": "ExampleValue", + "valueType": "xs:string", + "value": "VDI2770:2020" + } + ], + "valueType": "xs:string", + "value": "IEC-61360", + "modelType": "Property" + }, + { + "category": "PARAMETER", + "idShort": "ClassName", + "displayName": [ + { + "language": "en", + "text": "Class Name" + }, + { + "language": "de", + "text": "Klassenname" + } + ], + "semanticId": { + "type": "ExternalReference", + "keys": [ + { + "type": "GlobalReference", + "value": "0173-1#02-ABJ219#002" + } + ] + }, + "supplementalSemanticIds": [ + { + "type": "ExternalReference", + "keys": [ + { + "type": "GlobalReference", + "value": "https://api.eclass-cdp.com/0173-1-02-ABJ219-002" + } + ] + } + ], + "qualifiers": [ + { + "semanticId": { + "type": "ExternalReference", + "keys": [ + { + "type": "GlobalReference", + "value": "https://admin-shell.io/SubmodelTemplates/Cardinality/1/0" + } + ] + }, + "kind": "ConceptQualifier", + "type": "SMT/Cardinality", + "valueType": "xs:string", + "value": "One" + }, + { + "semanticId": { + "type": "ExternalReference", + "keys": [ + { + "type": "GlobalReference", + "value": "https://admin-shell.io/SubmodelTemplates/ExampleValue/1/0" + } + ] + }, + "kind": "ConceptQualifier", + "type": "ExampleValue", + "valueType": "xs:string", + "value": "Operation@en" + } + ], + "modelType": "MultiLanguageProperty" + } + ], + "modelType": "SubmodelElementCollection" + }, + { + "displayName": [ + { + "language": "en", + "text": "Document classification" + }, + { + "language": "de", + "text": "Dokumentklassifikation" + } + ], + "description": [ + { + "language": "en", + "text": "Set of information for describing the classification of the Document according to a ClassificationSystem" + } + ], + "semanticId": { + "type": "ExternalReference", + "keys": [ + { + "type": "GlobalReference", + "value": "0173-1#02-ABI502#003/0173-1#01-AHF581#003" + } + ] + }, + "supplementalSemanticIds": [ + { + "type": "ExternalReference", + "keys": [ + { + "type": "GlobalReference", + "value": "0173-1#02-ABI502#003~0/0173-1#01-AHF581#003" + }, + { + "type": "GlobalReference", + "value": "https://api.eclass-cdp.com/0173-1-02-ABI502-003/0173-1-01-AHF581-003" + } + ] + } + ], + "qualifiers": [ + { + "semanticId": { + "type": "ExternalReference", + "keys": [ + { + "type": "GlobalReference", + "value": "https://admin-shell.io/SubmodelTemplates/Cardinality/1/0" + } + ] + }, + "kind": "ConceptQualifier", + "type": "SMT/Cardinality", + "valueType": "xs:string", + "value": "OneToMany" + } + ], + "value": [ + { + "category": "PARAMETER", + "idShort": "ClassId", + "displayName": [ + { + "language": "en", + "text": "Class identificator" + }, + { + "language": "de", + "text": "Klassenidentifikator" + } + ], + "semanticId": { + "type": "ExternalReference", + "keys": [ + { + "type": "GlobalReference", + "value": "0173-1#02-ABH996#003" + } + ] + }, + "supplementalSemanticIds": [ + { + "type": "ExternalReference", + "keys": [ + { + "type": "GlobalReference", + "value": "https://api.eclass-cdp.com/0173-1-02-ABH996-003" + } + ] + } + ], + "qualifiers": [ + { + "semanticId": { + "type": "ExternalReference", + "keys": [ + { + "type": "GlobalReference", + "value": "https://admin-shell.io/SubmodelTemplates/Cardinality/1/0" + } + ] + }, + "kind": "ConceptQualifier", + "type": "SMT/Cardinality", + "valueType": "xs:string", + "value": "One" + }, + { + "semanticId": { + "type": "ExternalReference", + "keys": [ + { + "type": "GlobalReference", + "value": "https://admin-shell.io/SubmodelTemplates/ExampleValue/1/0" + } + ] + }, + "kind": "ConceptQualifier", + "type": "ExampleValue", + "valueType": "xs:string", + "value": "03-02" + } + ], + "valueType": "xs:string", + "value": "CLS-002", + "modelType": "Property" + }, + { + "category": "PARAMETER", + "idShort": "ClassificationSystem", + "displayName": [ + { + "language": "en", + "text": "Classification system" + }, + { + "language": "de", + "text": "Klassifizierungssystem" + } + ], + "semanticId": { + "type": "ExternalReference", + "keys": [ + { + "type": "GlobalReference", + "value": "0173-1#02-ABH997#003" + } + ] + }, + "supplementalSemanticIds": [ + { + "type": "ExternalReference", + "keys": [ + { + "type": "GlobalReference", + "value": "https://api.eclass-cdp.com/0173-1-02-ABH997-003" + } + ] + } + ], + "qualifiers": [ + { + "semanticId": { + "type": "ExternalReference", + "keys": [ + { + "type": "GlobalReference", + "value": "https://admin-shell.io/SubmodelTemplates/Cardinality/1/0" + } + ] + }, + "kind": "ConceptQualifier", + "type": "SMT/Cardinality", + "valueType": "xs:string", + "value": "One" + }, + { + "semanticId": { + "type": "ExternalReference", + "keys": [ + { + "type": "GlobalReference", + "value": "https://admin-shell.io/SubmodelTemplates/ExampleValue/1/0" + } + ] + }, + "kind": "ConceptQualifier", + "type": "ExampleValue", + "valueType": "xs:string", + "value": "VDI2770:2020" + } + ], + "valueType": "xs:string", + "value": "ISO-13584", + "modelType": "Property" + }, + { + "category": "PARAMETER", + "idShort": "ClassName", + "displayName": [ + { + "language": "en", + "text": "Class Name" + }, + { + "language": "de", + "text": "Klassenname" + } + ], + "semanticId": { + "type": "ExternalReference", + "keys": [ + { + "type": "GlobalReference", + "value": "0173-1#02-ABJ219#002" + } + ] + }, + "supplementalSemanticIds": [ + { + "type": "ExternalReference", + "keys": [ + { + "type": "GlobalReference", + "value": "https://api.eclass-cdp.com/0173-1-02-ABJ219-002" + } + ] + } + ], + "qualifiers": [ + { + "semanticId": { + "type": "ExternalReference", + "keys": [ + { + "type": "GlobalReference", + "value": "https://admin-shell.io/SubmodelTemplates/Cardinality/1/0" + } + ] + }, + "kind": "ConceptQualifier", + "type": "SMT/Cardinality", + "valueType": "xs:string", + "value": "One" + }, + { + "semanticId": { + "type": "ExternalReference", + "keys": [ + { + "type": "GlobalReference", + "value": "https://admin-shell.io/SubmodelTemplates/ExampleValue/1/0" + } + ] + }, + "kind": "ConceptQualifier", + "type": "ExampleValue", + "valueType": "xs:string", + "value": "Operation@en" + } + ], + "modelType": "MultiLanguageProperty" + } + ], + "modelType": "SubmodelElementCollection" + } + ], + "modelType": "SubmodelElementList" + }, + { + "idShort": "DocumentVersions", + "displayName": [ + { + "language": "en", + "text": "Document versions" + }, + { + "language": "de", + "text": "Dokumentenversionen" + } + ], + "semanticId": { + "type": "ExternalReference", + "keys": [ + { + "type": "GlobalReference", + "value": "0173-1#02-ABI503#003" + } + ] + }, + "supplementalSemanticIds": [ + { + "type": "ExternalReference", + "keys": [ + { + "type": "GlobalReference", + "value": "https://api.eclass-cdp.com/0173-1-02-ABI503-003" + } + ] + } + ], + "qualifiers": [ + { + "semanticId": { + "type": "ExternalReference", + "keys": [ + { + "type": "GlobalReference", + "value": "https://admin-shell.io/SubmodelTemplates/Cardinality/1/0" + } + ] + }, + "kind": "ConceptQualifier", + "type": "SMT/Cardinality", + "valueType": "xs:string", + "value": "One" + } + ], + "orderRelevant": false, + "semanticIdListElement": { + "type": "ExternalReference", + "keys": [ + { + "type": "GlobalReference", + "value": "0173-1#02-ABI503#003/0173-1#01-AHF582#003" + } + ] + }, + "typeValueListElement": "SubmodelElementCollection", + "value": [ + { + "displayName": [ + { + "language": "en", + "text": "Document version" + }, + { + "language": "de", + "text": "Document version" + } + ], + "semanticId": { + "type": "ExternalReference", + "keys": [ + { + "type": "GlobalReference", + "value": "0173-1#02-ABI503#003/0173-1#01-AHF582#003" + } + ] + }, + "supplementalSemanticIds": [ + { + "type": "ExternalReference", + "keys": [ + { + "type": "GlobalReference", + "value": "0173-1#02-ABI503#003~0/0173-1#01-AHF582#003" + }, + { + "type": "GlobalReference", + "value": "https://api.eclass-cdp.com/0173-1-02-ABI503-003/0173-1-01-AHF582-003" + } + ] + } + ], + "qualifiers": [ + { + "semanticId": { + "type": "ExternalReference", + "keys": [ + { + "type": "GlobalReference", + "value": "https://admin-shell.io/SubmodelTemplates/Cardinality/1/0" + } + ] + }, + "kind": "ConceptQualifier", + "type": "SMT/Cardinality", + "valueType": "xs:string", + "value": "OneToMany" + } + ], + "value": [ + { + "idShort": "Language", + "displayName": [ + { + "language": "en", + "text": "Language" + }, + { + "language": "de", + "text": "Sprache" + } + ], + "semanticId": { + "type": "ExternalReference", + "keys": [ + { + "type": "GlobalReference", + "value": "0173-1#02-AAN468#008" + } + ] + }, + "supplementalSemanticIds": [ + { + "type": "ExternalReference", + "keys": [ + { + "type": "GlobalReference", + "value": "https://api.eclass-cdp.com/0173-1-02-AAN468-008" + } + ] + } + ], + "qualifiers": [ + { + "semanticId": { + "type": "ExternalReference", + "keys": [ + { + "type": "GlobalReference", + "value": "https://admin-shell.io/SubmodelTemplates/Cardinality/1/0" + } + ] + }, + "kind": "ConceptQualifier", + "type": "SMT/Cardinality", + "valueType": "xs:string", + "value": "One" + } + ], + "orderRelevant": false, + "typeValueListElement": "Property", + "valueTypeListElement": "xs:string", + "value": [ + { + "displayName": [ + { + "language": "en", + "text": "en (English)" + }, + { + "language": "de", + "text": "en (Englisch)" + } + ], + "semanticId": { + "type": "ExternalReference", + "keys": [ + { + "type": "GlobalReference", + "value": "0173-1#02-AAN468#008" + } + ] + }, + "qualifiers": [ + { + "semanticId": { + "type": "ExternalReference", + "keys": [ + { + "type": "GlobalReference", + "value": "https://admin-shell.io/SubmodelTemplates/Cardinality/1/0" + } + ] + }, + "kind": "ConceptQualifier", + "type": "SMT/Cardinality", + "valueType": "xs:string", + "value": "OneToMany" + }, + { + "semanticId": { + "type": "ExternalReference", + "keys": [ + { + "type": "GlobalReference", + "value": "https://admin-shell.io/SubmodelTemplates/ExampleValue/1/0" + } + ] + }, + "kind": "ConceptQualifier", + "type": "ExampleValue", + "valueType": "xs:string", + "value": "en" + } + ], + "valueType": "xs:string", + "value": "en", + "valueId": { + "type": "ExternalReference", + "keys": [ + { + "type": "GlobalReference", + "value": "0173-1#07-AAS045#003" + } + ] + }, + "modelType": "Property" + } + ], + "modelType": "SubmodelElementList" + }, + { + "idShort": "DigitalFiles", + "displayName": [ + { + "language": "en", + "text": "Digital files" + }, + { + "language": "de", + "text": "Digitale Dateien" + } + ], + "semanticId": { + "type": "ExternalReference", + "keys": [ + { + "type": "GlobalReference", + "value": "0173-1#02-ABK126#002" + } + ] + }, + "supplementalSemanticIds": [ + { + "type": "ExternalReference", + "keys": [ + { + "type": "GlobalReference", + "value": "https://api.eclass-cdp.com/0173-1-02-ABK126-002" + } + ] + } + ], + "qualifiers": [ + { + "semanticId": { + "type": "ExternalReference", + "keys": [ + { + "type": "GlobalReference", + "value": "https://admin-shell.io/SubmodelTemplates/ExampleValue/1/0" + } + ] + }, + "kind": "ConceptQualifier", + "type": "SMT/Cardinality", + "valueType": "xs:string", + "value": "One" + } + ], + "orderRelevant": false, + "typeValueListElement": "File", + "value": [ + { + "displayName": [ + { + "language": "en", + "text": "Name of the specific digital file@en" + }, + { + "language": "de", + "text": "Name der spezifischen digitalen Datei@de" + } + ], + "semanticId": { + "type": "ExternalReference", + "keys": [ + { + "type": "GlobalReference", + "value": "0173-1#02-ABK126#002" + } + ] + }, + "qualifiers": [ + { + "semanticId": { + "type": "ExternalReference", + "keys": [ + { + "type": "GlobalReference", + "value": "https://admin-shell.io/SubmodelTemplates/Cardinality/1/0" + } + ] + }, + "kind": "ConceptQualifier", + "type": "SMT/Cardinality", + "valueType": "xs:string", + "value": "OneToMany" + }, + { + "semanticId": { + "type": "ExternalReference", + "keys": [ + { + "type": "GlobalReference", + "value": "https://admin-shell.io/SubmodelTemplates/ExampleValue/1/0" + } + ] + }, + "kind": "ConceptQualifier", + "type": "ExampleValue", + "valueType": "xs:string", + "value": "docu_cecc_fullmanual_DE.PDF" + }, + { + "semanticId": { + "type": "ExternalReference", + "keys": [ + { + "type": "GlobalReference", + "value": "https://admin-shell.io/SubmodelTemplates/AllowedIdShort/1/0" + } + ] + }, + "kind": "ConceptQualifier", + "type": "AllowedIdShort", + "valueType": "xs:string", + "value": "DigitalFile[\\d{2,3}]" + } + ], + "value": "https://docs.google.com/viewer?url=https://raw.githubusercontent.com/AAS-TwinEngine/AAS.TwinEngine.DataEngine/refs/heads/main/example/data/dummy_document.pdf", + "contentType": "application/pdf", + "modelType": "File" + } + ], + "modelType": "SubmodelElementList" + }, + { + "category": "PARAMETER", + "idShort": "Version", + "displayName": [ + { + "language": "en", + "text": "Document version" + }, + { + "language": "de", + "text": "Dokumentenversion" + } + ], + "semanticId": { + "type": "ExternalReference", + "keys": [ + { + "type": "GlobalReference", + "value": "0173-1#02-AAP003#005" + } + ] + }, + "supplementalSemanticIds": [ + { + "type": "ExternalReference", + "keys": [ + { + "type": "GlobalReference", + "value": "https://api.eclass-cdp.com/0173-1-02-AAP003-005" + } + ] + } + ], + "qualifiers": [ + { + "semanticId": { + "type": "ExternalReference", + "keys": [ + { + "type": "GlobalReference", + "value": "https://admin-shell.io/SubmodelTemplates/Cardinality/1/0" + } + ] + }, + "kind": "ConceptQualifier", + "type": "SMT/Cardinality", + "valueType": "xs:string", + "value": "One" + }, + { + "semanticId": { + "type": "ExternalReference", + "keys": [ + { + "type": "GlobalReference", + "value": "https://admin-shell.io/SubmodelTemplates/ExampleValue/1/0" + } + ] + }, + "kind": "ConceptQualifier", + "type": "ExampleValue", + "valueType": "xs:string", + "value": "V1.2" + } + ], + "valueType": "xs:string", + "value": "1", + "modelType": "Property" + }, + { + "category": "PARAMETER", + "idShort": "StatusSetDate", + "displayName": [ + { + "language": "en", + "text": "Document status set date" + }, + { + "language": "de", + "text": "Datum der Einstellung des Dokumentenstatus" + } + ], + "semanticId": { + "type": "ExternalReference", + "keys": [ + { + "type": "GlobalReference", + "value": "0173-1#02-ABI000#003" + } + ] + }, + "supplementalSemanticIds": [ + { + "type": "ExternalReference", + "keys": [ + { + "type": "GlobalReference", + "value": "https://api.eclass-cdp.com/0173-1-02-ABI000-003" + } + ] + } + ], + "qualifiers": [ + { + "semanticId": { + "type": "ExternalReference", + "keys": [ + { + "type": "GlobalReference", + "value": "https://admin-shell.io/SubmodelTemplates/Cardinality/1/0" + } + ] + }, + "kind": "ConceptQualifier", + "type": "SMT/Cardinality", + "valueType": "xs:string", + "value": "One" + }, + { + "semanticId": { + "type": "ExternalReference", + "keys": [ + { + "type": "GlobalReference", + "value": "https://admin-shell.io/SubmodelTemplates/ExampleValue/1/0" + } + ] + }, + "kind": "ConceptQualifier", + "type": "ExampleValue", + "valueType": "xs:string", + "value": "2020-02-06" + } + ], + "valueType": "xs:date", + "value": "2023-01-01", + "modelType": "Property" + }, + { + "category": "PARAMETER", + "idShort": "StatusValue", + "displayName": [ + { + "language": "en", + "text": "Document status" + }, + { + "language": "de", + "text": "Dokumentstatus" + } + ], + "semanticId": { + "type": "ExternalReference", + "keys": [ + { + "type": "GlobalReference", + "value": "0173-1#02-ABI001#003" + } + ] + }, + "supplementalSemanticIds": [ + { + "type": "ExternalReference", + "keys": [ + { + "type": "GlobalReference", + "value": "https://api.eclass-cdp.com/0173-1-02-ABI001-003" + } + ] + } + ], + "qualifiers": [ + { + "semanticId": { + "type": "ExternalReference", + "keys": [ + { + "type": "GlobalReference", + "value": "https://admin-shell.io/SubmodelTemplates/Cardinality/1/0" + } + ] + }, + "kind": "ConceptQualifier", + "type": "SMT/Cardinality", + "valueType": "xs:string", + "value": "One" + }, + { + "semanticId": { + "type": "ExternalReference", + "keys": [ + { + "type": "GlobalReference", + "value": "https://admin-shell.io/SubmodelTemplates/ExampleValue/1/0" + } + ] + }, + "kind": "ConceptQualifier", + "type": "ExampleValue", + "valueType": "xs:string", + "value": "Released" + } + ], + "valueType": "xs:string", + "value": "Released", + "modelType": "Property" + }, + { + "category": "PARAMETER", + "idShort": "OrganizationShortName", + "displayName": [ + { + "language": "en", + "text": "Organization short name" + }, + { + "language": "de", + "text": "Kurzname der Organisation" + } + ], + "semanticId": { + "type": "ExternalReference", + "keys": [ + { + "type": "GlobalReference", + "value": "https://api.eclass-cdp.com/0173-1-02-ABI002-003" + } + ] + }, + "qualifiers": [ + { + "semanticId": { + "type": "ExternalReference", + "keys": [ + { + "type": "GlobalReference", + "value": "https://admin-shell.io/SubmodelTemplates/Cardinality/1/0" + } + ] + }, + "kind": "ConceptQualifier", + "type": "SMT/Cardinality", + "valueType": "xs:string", + "value": "One" + }, + { + "semanticId": { + "type": "ExternalReference", + "keys": [ + { + "type": "GlobalReference", + "value": "https://admin-shell.io/SubmodelTemplates/ExampleValue/1/0" + } + ] + }, + "kind": "ConceptQualifier", + "type": "ExampleValue", + "valueType": "xs:string", + "value": "Example company" + } + ], + "valueType": "xs:string", + "value": "M&M", + "modelType": "Property" + }, + { + "category": "PARAMETER", + "idShort": "OrganizationOfficialName", + "displayName": [ + { + "language": "en", + "text": "Organization official name" + }, + { + "language": "de", + "text": "Offizieller Name der Organisation" + } + ], + "semanticId": { + "type": "ExternalReference", + "keys": [ + { + "type": "GlobalReference", + "value": "0173-1#02-ABI004#003" + } + ] + }, + "supplementalSemanticIds": [ + { + "type": "ExternalReference", + "keys": [ + { + "type": "GlobalReference", + "value": "https://api.eclass-cdp.com/0173-1-02-ABI004-003" + } + ] + } + ], + "qualifiers": [ + { + "semanticId": { + "type": "ExternalReference", + "keys": [ + { + "type": "GlobalReference", + "value": "https://admin-shell.io/SubmodelTemplates/Cardinality/1/0" + } + ] + }, + "kind": "ConceptQualifier", + "type": "SMT/Cardinality", + "valueType": "xs:string", + "value": "One" + }, + { + "semanticId": { + "type": "ExternalReference", + "keys": [ + { + "type": "GlobalReference", + "value": "https://admin-shell.io/SubmodelTemplates/ExampleValue/1/0" + } + ] + }, + "kind": "ConceptQualifier", + "type": "ExampleValue", + "valueType": "xs:string", + "value": "Example company Ltd." + } + ], + "valueType": "xs:string", + "value": "M&M Germany", + "modelType": "Property" + }, + { + "category": "PARAMETER", + "idShort": "Title", + "displayName": [ + { + "language": "en", + "text": "Document title" + }, + { + "language": "de", + "text": "Dokumententitel" + } + ], + "semanticId": { + "type": "ExternalReference", + "keys": [ + { + "type": "GlobalReference", + "value": "0173-1#02-ABG940#003" + } + ] + }, + "supplementalSemanticIds": [ + { + "type": "ExternalReference", + "keys": [ + { + "type": "GlobalReference", + "value": "https://api.eclass-cdp.com/0173-1-02-ABG940-003" + } + ] + } + ], + "qualifiers": [ + { + "semanticId": { + "type": "ExternalReference", + "keys": [ + { + "type": "GlobalReference", + "value": "https://admin-shell.io/SubmodelTemplates/Cardinality/1/0" + } + ] + }, + "kind": "ConceptQualifier", + "type": "SMT/Cardinality", + "valueType": "xs:string", + "value": "One" + }, + { + "semanticId": { + "type": "ExternalReference", + "keys": [ + { + "type": "GlobalReference", + "value": "https://admin-shell.io/SubmodelTemplates/ExampleValue/1/0" + } + ] + }, + "kind": "ConceptQualifier", + "type": "ExampleValue", + "valueType": "xs:string", + "value": "Examplary title@en" + } + ], + "modelType": "MultiLanguageProperty" + }, + { + "category": "PARAMETER", + "idShort": "Subtitle", + "displayName": [ + { + "language": "en", + "text": "Subtitle" + }, + { + "language": "de", + "text": "Untertitel" + } + ], + "semanticId": { + "type": "ExternalReference", + "keys": [ + { + "type": "GlobalReference", + "value": "0173-1#02-ABH998#003" + } + ] + }, + "supplementalSemanticIds": [ + { + "type": "ExternalReference", + "keys": [ + { + "type": "GlobalReference", + "value": "https://api.eclass-cdp.com/0173-1-02-ABH998-003" + } + ] + } + ], + "qualifiers": [ + { + "semanticId": { + "type": "ExternalReference", + "keys": [ + { + "type": "GlobalReference", + "value": "https://admin-shell.io/SubmodelTemplates/Cardinality/1/0" + } + ] + }, + "kind": "ConceptQualifier", + "type": "SMT/Cardinality", + "valueType": "xs:string", + "value": "ZeroToOne" + }, + { + "semanticId": { + "type": "ExternalReference", + "keys": [ + { + "type": "GlobalReference", + "value": "https://admin-shell.io/SubmodelTemplates/ExampleValue/1/0" + } + ] + }, + "kind": "ConceptQualifier", + "type": "ExampleValue", + "valueType": "xs:string", + "value": "Examplary subtitle@en" + } + ], + "modelType": "MultiLanguageProperty" + }, + { + "category": "PARAMETER", + "idShort": "Description", + "displayName": [ + { + "language": "en", + "text": "Document description" + }, + { + "language": "de", + "text": "Dokumentenbeschreibung" + } + ], + "semanticId": { + "type": "ExternalReference", + "keys": [ + { + "type": "GlobalReference", + "value": "0173-1#02-AAN466#004" + } + ] + }, + "supplementalSemanticIds": [ + { + "type": "ExternalReference", + "keys": [ + { + "type": "GlobalReference", + "value": "https://api.eclass-cdp.com/0173-1-02-AAN466-004" + } + ] + } + ], + "qualifiers": [ + { + "semanticId": { + "type": "ExternalReference", + "keys": [ + { + "type": "GlobalReference", + "value": "https://admin-shell.io/SubmodelTemplates/Cardinality/1/0" + } + ] + }, + "kind": "ConceptQualifier", + "type": "SMT/Cardinality", + "valueType": "xs:string", + "value": "One" + }, + { + "semanticId": { + "type": "ExternalReference", + "keys": [ + { + "type": "GlobalReference", + "value": "https://admin-shell.io/SubmodelTemplates/ExampleValue/1/0" + } + ] + }, + "kind": "ConceptQualifier", + "type": "ExampleValue", + "valueType": "xs:string", + "value": "Abstract@en" + } + ], + "modelType": "MultiLanguageProperty" + }, + { + "category": "PARAMETER", + "idShort": "KeyWords", + "displayName": [ + { + "language": "en", + "text": "Keywords" + }, + { + "language": "de", + "text": "Stichworte" + } + ], + "semanticId": { + "type": "ExternalReference", + "keys": [ + { + "type": "GlobalReference", + "value": "0173-1#02-ABH999#003" + } + ] + }, + "supplementalSemanticIds": [ + { + "type": "ExternalReference", + "keys": [ + { + "type": "GlobalReference", + "value": "https://api.eclass-cdp.com/0173-1-02-ABH999-003" + } + ] + } + ], + "qualifiers": [ + { + "semanticId": { + "type": "ExternalReference", + "keys": [ + { + "type": "GlobalReference", + "value": "https://admin-shell.io/SubmodelTemplates/Cardinality/1/0" + } + ] + }, + "kind": "ConceptQualifier", + "type": "SMT/Cardinality", + "valueType": "xs:string", + "value": "ZeroToOne" + }, + { + "semanticId": { + "type": "ExternalReference", + "keys": [ + { + "type": "GlobalReference", + "value": "https://admin-shell.io/SubmodelTemplates/ExampleValue/1/0" + } + ] + }, + "kind": "ConceptQualifier", + "type": "ExampleValue", + "valueType": "xs:string", + "value": "Examplary keywords@en" + } + ], + "modelType": "MultiLanguageProperty" + }, + { + "category": "PARAMETER", + "idShort": "PreviewFile", + "displayName": [ + { + "language": "en", + "text": "Preview file" + }, + { + "language": "de", + "text": "Vorschaudatei" + } + ], + "semanticId": { + "type": "ExternalReference", + "keys": [ + { + "type": "GlobalReference", + "value": "0173-1#02-ABK127#002" + } + ] + }, + "supplementalSemanticIds": [ + { + "type": "ExternalReference", + "keys": [ + { + "type": "GlobalReference", + "value": "https://api.eclass-cdp.com/0173-1-02-ABK127-002" + } + ] + } + ], + "qualifiers": [ + { + "semanticId": { + "type": "ExternalReference", + "keys": [ + { + "type": "GlobalReference", + "value": "https://admin-shell.io/SubmodelTemplates/Cardinality/1/0" + } + ] + }, + "kind": "ConceptQualifier", + "type": "SMT/Cardinality", + "valueType": "xs:string", + "value": "ZeroToOne" + }, + { + "semanticId": { + "type": "ExternalReference", + "keys": [ + { + "type": "GlobalReference", + "value": "https://admin-shell.io/SubmodelTemplates/ExampleValue/1/0" + } + ] + }, + "kind": "ConceptQualifier", + "type": "ExampleValue", + "valueType": "xs:string", + "value": "docu_cecc_fullmanual_DE.jpg" + }, + { + "semanticId": { + "type": "ExternalReference", + "keys": [ + { + "type": "GlobalReference", + "value": "https://admin-shell.io/SubmodelTemplates/AllowedIdShort/1/0" + } + ] + }, + "kind": "ConceptQualifier", + "type": "AllowedIdShort", + "valueType": "xs:string", + "value": "PreviewFile[\\d{2,3}]" + } + ], + "value": "https://raw.githubusercontent.com/AAS-TwinEngine/AAS.TwinEngine.DataEngine/refs/heads/main/example/data/dummy_document.jpg", + "contentType": "image/jpeg", + "modelType": "File" + } + ], + "modelType": "SubmodelElementCollection" + }, + { + "displayName": [ + { + "language": "en", + "text": "Document version" + }, + { + "language": "de", + "text": "Document version" + } + ], + "semanticId": { + "type": "ExternalReference", + "keys": [ + { + "type": "GlobalReference", + "value": "0173-1#02-ABI503#003/0173-1#01-AHF582#003" + } + ] + }, + "supplementalSemanticIds": [ + { + "type": "ExternalReference", + "keys": [ + { + "type": "GlobalReference", + "value": "0173-1#02-ABI503#003~0/0173-1#01-AHF582#003" + }, + { + "type": "GlobalReference", + "value": "https://api.eclass-cdp.com/0173-1-02-ABI503-003/0173-1-01-AHF582-003" + } + ] + } + ], + "qualifiers": [ + { + "semanticId": { + "type": "ExternalReference", + "keys": [ + { + "type": "GlobalReference", + "value": "https://admin-shell.io/SubmodelTemplates/Cardinality/1/0" + } + ] + }, + "kind": "ConceptQualifier", + "type": "SMT/Cardinality", + "valueType": "xs:string", + "value": "OneToMany" + } + ], + "value": [ + { + "idShort": "Language", + "displayName": [ + { + "language": "en", + "text": "Language" + }, + { + "language": "de", + "text": "Sprache" + } + ], + "semanticId": { + "type": "ExternalReference", + "keys": [ + { + "type": "GlobalReference", + "value": "0173-1#02-AAN468#008" + } + ] + }, + "supplementalSemanticIds": [ + { + "type": "ExternalReference", + "keys": [ + { + "type": "GlobalReference", + "value": "https://api.eclass-cdp.com/0173-1-02-AAN468-008" + } + ] + } + ], + "qualifiers": [ + { + "semanticId": { + "type": "ExternalReference", + "keys": [ + { + "type": "GlobalReference", + "value": "https://admin-shell.io/SubmodelTemplates/Cardinality/1/0" + } + ] + }, + "kind": "ConceptQualifier", + "type": "SMT/Cardinality", + "valueType": "xs:string", + "value": "One" + } + ], + "orderRelevant": false, + "typeValueListElement": "Property", + "valueTypeListElement": "xs:string", + "value": [ + { + "displayName": [ + { + "language": "en", + "text": "en (English)" + }, + { + "language": "de", + "text": "en (Englisch)" + } + ], + "semanticId": { + "type": "ExternalReference", + "keys": [ + { + "type": "GlobalReference", + "value": "0173-1#02-AAN468#008" + } + ] + }, + "qualifiers": [ + { + "semanticId": { + "type": "ExternalReference", + "keys": [ + { + "type": "GlobalReference", + "value": "https://admin-shell.io/SubmodelTemplates/Cardinality/1/0" + } + ] + }, + "kind": "ConceptQualifier", + "type": "SMT/Cardinality", + "valueType": "xs:string", + "value": "OneToMany" + }, + { + "semanticId": { + "type": "ExternalReference", + "keys": [ + { + "type": "GlobalReference", + "value": "https://admin-shell.io/SubmodelTemplates/ExampleValue/1/0" + } + ] + }, + "kind": "ConceptQualifier", + "type": "ExampleValue", + "valueType": "xs:string", + "value": "en" + } + ], + "valueType": "xs:string", + "value": "de", + "valueId": { + "type": "ExternalReference", + "keys": [ + { + "type": "GlobalReference", + "value": "0173-1#07-AAS045#003" + } + ] + }, + "modelType": "Property" + } + ], + "modelType": "SubmodelElementList" + }, + { + "idShort": "DigitalFiles", + "displayName": [ + { + "language": "en", + "text": "Digital files" + }, + { + "language": "de", + "text": "Digitale Dateien" + } + ], + "semanticId": { + "type": "ExternalReference", + "keys": [ + { + "type": "GlobalReference", + "value": "0173-1#02-ABK126#002" + } + ] + }, + "supplementalSemanticIds": [ + { + "type": "ExternalReference", + "keys": [ + { + "type": "GlobalReference", + "value": "https://api.eclass-cdp.com/0173-1-02-ABK126-002" + } + ] + } + ], + "qualifiers": [ + { + "semanticId": { + "type": "ExternalReference", + "keys": [ + { + "type": "GlobalReference", + "value": "https://admin-shell.io/SubmodelTemplates/ExampleValue/1/0" + } + ] + }, + "kind": "ConceptQualifier", + "type": "SMT/Cardinality", + "valueType": "xs:string", + "value": "One" + } + ], + "orderRelevant": false, + "typeValueListElement": "File", + "value": [ + { + "displayName": [ + { + "language": "en", + "text": "Name of the specific digital file@en" + }, + { + "language": "de", + "text": "Name der spezifischen digitalen Datei@de" + } + ], + "semanticId": { + "type": "ExternalReference", + "keys": [ + { + "type": "GlobalReference", + "value": "0173-1#02-ABK126#002" + } + ] + }, + "qualifiers": [ + { + "semanticId": { + "type": "ExternalReference", + "keys": [ + { + "type": "GlobalReference", + "value": "https://admin-shell.io/SubmodelTemplates/Cardinality/1/0" + } + ] + }, + "kind": "ConceptQualifier", + "type": "SMT/Cardinality", + "valueType": "xs:string", + "value": "OneToMany" + }, + { + "semanticId": { + "type": "ExternalReference", + "keys": [ + { + "type": "GlobalReference", + "value": "https://admin-shell.io/SubmodelTemplates/ExampleValue/1/0" + } + ] + }, + "kind": "ConceptQualifier", + "type": "ExampleValue", + "valueType": "xs:string", + "value": "docu_cecc_fullmanual_DE.PDF" + }, + { + "semanticId": { + "type": "ExternalReference", + "keys": [ + { + "type": "GlobalReference", + "value": "https://admin-shell.io/SubmodelTemplates/AllowedIdShort/1/0" + } + ] + }, + "kind": "ConceptQualifier", + "type": "AllowedIdShort", + "valueType": "xs:string", + "value": "DigitalFile[\\d{2,3}]" + } + ], + "value": "https://docs.google.com/viewer?url=https://raw.githubusercontent.com/AAS-TwinEngine/AAS.TwinEngine.DataEngine/refs/heads/main/example/data/dummy_document.pdf", + "contentType": "application/pdf", + "modelType": "File" + } + ], + "modelType": "SubmodelElementList" + }, + { + "category": "PARAMETER", + "idShort": "Version", + "displayName": [ + { + "language": "en", + "text": "Document version" + }, + { + "language": "de", + "text": "Dokumentenversion" + } + ], + "semanticId": { + "type": "ExternalReference", + "keys": [ + { + "type": "GlobalReference", + "value": "0173-1#02-AAP003#005" + } + ] + }, + "supplementalSemanticIds": [ + { + "type": "ExternalReference", + "keys": [ + { + "type": "GlobalReference", + "value": "https://api.eclass-cdp.com/0173-1-02-AAP003-005" + } + ] + } + ], + "qualifiers": [ + { + "semanticId": { + "type": "ExternalReference", + "keys": [ + { + "type": "GlobalReference", + "value": "https://admin-shell.io/SubmodelTemplates/Cardinality/1/0" + } + ] + }, + "kind": "ConceptQualifier", + "type": "SMT/Cardinality", + "valueType": "xs:string", + "value": "One" + }, + { + "semanticId": { + "type": "ExternalReference", + "keys": [ + { + "type": "GlobalReference", + "value": "https://admin-shell.io/SubmodelTemplates/ExampleValue/1/0" + } + ] + }, + "kind": "ConceptQualifier", + "type": "ExampleValue", + "valueType": "xs:string", + "value": "V1.2" + } + ], + "valueType": "xs:string", + "value": "1.1", + "modelType": "Property" + }, + { + "category": "PARAMETER", + "idShort": "StatusSetDate", + "displayName": [ + { + "language": "en", + "text": "Document status set date" + }, + { + "language": "de", + "text": "Datum der Einstellung des Dokumentenstatus" + } + ], + "semanticId": { + "type": "ExternalReference", + "keys": [ + { + "type": "GlobalReference", + "value": "0173-1#02-ABI000#003" + } + ] + }, + "supplementalSemanticIds": [ + { + "type": "ExternalReference", + "keys": [ + { + "type": "GlobalReference", + "value": "https://api.eclass-cdp.com/0173-1-02-ABI000-003" + } + ] + } + ], + "qualifiers": [ + { + "semanticId": { + "type": "ExternalReference", + "keys": [ + { + "type": "GlobalReference", + "value": "https://admin-shell.io/SubmodelTemplates/Cardinality/1/0" + } + ] + }, + "kind": "ConceptQualifier", + "type": "SMT/Cardinality", + "valueType": "xs:string", + "value": "One" + }, + { + "semanticId": { + "type": "ExternalReference", + "keys": [ + { + "type": "GlobalReference", + "value": "https://admin-shell.io/SubmodelTemplates/ExampleValue/1/0" + } + ] + }, + "kind": "ConceptQualifier", + "type": "ExampleValue", + "valueType": "xs:string", + "value": "2020-02-06" + } + ], + "valueType": "xs:date", + "value": "2024-05-05", + "modelType": "Property" + }, + { + "category": "PARAMETER", + "idShort": "StatusValue", + "displayName": [ + { + "language": "en", + "text": "Document status" + }, + { + "language": "de", + "text": "Dokumentstatus" + } + ], + "semanticId": { + "type": "ExternalReference", + "keys": [ + { + "type": "GlobalReference", + "value": "0173-1#02-ABI001#003" + } + ] + }, + "supplementalSemanticIds": [ + { + "type": "ExternalReference", + "keys": [ + { + "type": "GlobalReference", + "value": "https://api.eclass-cdp.com/0173-1-02-ABI001-003" + } + ] + } + ], + "qualifiers": [ + { + "semanticId": { + "type": "ExternalReference", + "keys": [ + { + "type": "GlobalReference", + "value": "https://admin-shell.io/SubmodelTemplates/Cardinality/1/0" + } + ] + }, + "kind": "ConceptQualifier", + "type": "SMT/Cardinality", + "valueType": "xs:string", + "value": "One" + }, + { + "semanticId": { + "type": "ExternalReference", + "keys": [ + { + "type": "GlobalReference", + "value": "https://admin-shell.io/SubmodelTemplates/ExampleValue/1/0" + } + ] + }, + "kind": "ConceptQualifier", + "type": "ExampleValue", + "valueType": "xs:string", + "value": "Released" + } + ], + "valueType": "xs:string", + "value": "InReview", + "modelType": "Property" + }, + { + "category": "PARAMETER", + "idShort": "OrganizationShortName", + "displayName": [ + { + "language": "en", + "text": "Organization short name" + }, + { + "language": "de", + "text": "Kurzname der Organisation" + } + ], + "semanticId": { + "type": "ExternalReference", + "keys": [ + { + "type": "GlobalReference", + "value": "https://api.eclass-cdp.com/0173-1-02-ABI002-003" + } + ] + }, + "qualifiers": [ + { + "semanticId": { + "type": "ExternalReference", + "keys": [ + { + "type": "GlobalReference", + "value": "https://admin-shell.io/SubmodelTemplates/Cardinality/1/0" + } + ] + }, + "kind": "ConceptQualifier", + "type": "SMT/Cardinality", + "valueType": "xs:string", + "value": "One" + }, + { + "semanticId": { + "type": "ExternalReference", + "keys": [ + { + "type": "GlobalReference", + "value": "https://admin-shell.io/SubmodelTemplates/ExampleValue/1/0" + } + ] + }, + "kind": "ConceptQualifier", + "type": "ExampleValue", + "valueType": "xs:string", + "value": "Example company" + } + ], + "valueType": "xs:string", + "value": "M&M", + "modelType": "Property" + }, + { + "category": "PARAMETER", + "idShort": "OrganizationOfficialName", + "displayName": [ + { + "language": "en", + "text": "Organization official name" + }, + { + "language": "de", + "text": "Offizieller Name der Organisation" + } + ], + "semanticId": { + "type": "ExternalReference", + "keys": [ + { + "type": "GlobalReference", + "value": "0173-1#02-ABI004#003" + } + ] + }, + "supplementalSemanticIds": [ + { + "type": "ExternalReference", + "keys": [ + { + "type": "GlobalReference", + "value": "https://api.eclass-cdp.com/0173-1-02-ABI004-003" + } + ] + } + ], + "qualifiers": [ + { + "semanticId": { + "type": "ExternalReference", + "keys": [ + { + "type": "GlobalReference", + "value": "https://admin-shell.io/SubmodelTemplates/Cardinality/1/0" + } + ] + }, + "kind": "ConceptQualifier", + "type": "SMT/Cardinality", + "valueType": "xs:string", + "value": "One" + }, + { + "semanticId": { + "type": "ExternalReference", + "keys": [ + { + "type": "GlobalReference", + "value": "https://admin-shell.io/SubmodelTemplates/ExampleValue/1/0" + } + ] + }, + "kind": "ConceptQualifier", + "type": "ExampleValue", + "valueType": "xs:string", + "value": "Example company Ltd." + } + ], + "valueType": "xs:string", + "value": "M&M India", + "modelType": "Property" + }, + { + "category": "PARAMETER", + "idShort": "Title", + "displayName": [ + { + "language": "en", + "text": "Document title" + }, + { + "language": "de", + "text": "Dokumententitel" + } + ], + "semanticId": { + "type": "ExternalReference", + "keys": [ + { + "type": "GlobalReference", + "value": "0173-1#02-ABG940#003" + } + ] + }, + "supplementalSemanticIds": [ + { + "type": "ExternalReference", + "keys": [ + { + "type": "GlobalReference", + "value": "https://api.eclass-cdp.com/0173-1-02-ABG940-003" + } + ] + } + ], + "qualifiers": [ + { + "semanticId": { + "type": "ExternalReference", + "keys": [ + { + "type": "GlobalReference", + "value": "https://admin-shell.io/SubmodelTemplates/Cardinality/1/0" + } + ] + }, + "kind": "ConceptQualifier", + "type": "SMT/Cardinality", + "valueType": "xs:string", + "value": "One" + }, + { + "semanticId": { + "type": "ExternalReference", + "keys": [ + { + "type": "GlobalReference", + "value": "https://admin-shell.io/SubmodelTemplates/ExampleValue/1/0" + } + ] + }, + "kind": "ConceptQualifier", + "type": "ExampleValue", + "valueType": "xs:string", + "value": "Examplary title@en" + } + ], + "modelType": "MultiLanguageProperty" + }, + { + "category": "PARAMETER", + "idShort": "Subtitle", + "displayName": [ + { + "language": "en", + "text": "Subtitle" + }, + { + "language": "de", + "text": "Untertitel" + } + ], + "semanticId": { + "type": "ExternalReference", + "keys": [ + { + "type": "GlobalReference", + "value": "0173-1#02-ABH998#003" + } + ] + }, + "supplementalSemanticIds": [ + { + "type": "ExternalReference", + "keys": [ + { + "type": "GlobalReference", + "value": "https://api.eclass-cdp.com/0173-1-02-ABH998-003" + } + ] + } + ], + "qualifiers": [ + { + "semanticId": { + "type": "ExternalReference", + "keys": [ + { + "type": "GlobalReference", + "value": "https://admin-shell.io/SubmodelTemplates/Cardinality/1/0" + } + ] + }, + "kind": "ConceptQualifier", + "type": "SMT/Cardinality", + "valueType": "xs:string", + "value": "ZeroToOne" + }, + { + "semanticId": { + "type": "ExternalReference", + "keys": [ + { + "type": "GlobalReference", + "value": "https://admin-shell.io/SubmodelTemplates/ExampleValue/1/0" + } + ] + }, + "kind": "ConceptQualifier", + "type": "ExampleValue", + "valueType": "xs:string", + "value": "Examplary subtitle@en" + } + ], + "modelType": "MultiLanguageProperty" + }, + { + "category": "PARAMETER", + "idShort": "Description", + "displayName": [ + { + "language": "en", + "text": "Document description" + }, + { + "language": "de", + "text": "Dokumentenbeschreibung" + } + ], + "semanticId": { + "type": "ExternalReference", + "keys": [ + { + "type": "GlobalReference", + "value": "0173-1#02-AAN466#004" + } + ] + }, + "supplementalSemanticIds": [ + { + "type": "ExternalReference", + "keys": [ + { + "type": "GlobalReference", + "value": "https://api.eclass-cdp.com/0173-1-02-AAN466-004" + } + ] + } + ], + "qualifiers": [ + { + "semanticId": { + "type": "ExternalReference", + "keys": [ + { + "type": "GlobalReference", + "value": "https://admin-shell.io/SubmodelTemplates/Cardinality/1/0" + } + ] + }, + "kind": "ConceptQualifier", + "type": "SMT/Cardinality", + "valueType": "xs:string", + "value": "One" + }, + { + "semanticId": { + "type": "ExternalReference", + "keys": [ + { + "type": "GlobalReference", + "value": "https://admin-shell.io/SubmodelTemplates/ExampleValue/1/0" + } + ] + }, + "kind": "ConceptQualifier", + "type": "ExampleValue", + "valueType": "xs:string", + "value": "Abstract@en" + } + ], + "modelType": "MultiLanguageProperty" + }, + { + "category": "PARAMETER", + "idShort": "KeyWords", + "displayName": [ + { + "language": "en", + "text": "Keywords" + }, + { + "language": "de", + "text": "Stichworte" + } + ], + "semanticId": { + "type": "ExternalReference", + "keys": [ + { + "type": "GlobalReference", + "value": "0173-1#02-ABH999#003" + } + ] + }, + "supplementalSemanticIds": [ + { + "type": "ExternalReference", + "keys": [ + { + "type": "GlobalReference", + "value": "https://api.eclass-cdp.com/0173-1-02-ABH999-003" + } + ] + } + ], + "qualifiers": [ + { + "semanticId": { + "type": "ExternalReference", + "keys": [ + { + "type": "GlobalReference", + "value": "https://admin-shell.io/SubmodelTemplates/Cardinality/1/0" + } + ] + }, + "kind": "ConceptQualifier", + "type": "SMT/Cardinality", + "valueType": "xs:string", + "value": "ZeroToOne" + }, + { + "semanticId": { + "type": "ExternalReference", + "keys": [ + { + "type": "GlobalReference", + "value": "https://admin-shell.io/SubmodelTemplates/ExampleValue/1/0" + } + ] + }, + "kind": "ConceptQualifier", + "type": "ExampleValue", + "valueType": "xs:string", + "value": "Examplary keywords@en" + } + ], + "modelType": "MultiLanguageProperty" + }, + { + "category": "PARAMETER", + "idShort": "PreviewFile", + "displayName": [ + { + "language": "en", + "text": "Preview file" + }, + { + "language": "de", + "text": "Vorschaudatei" + } + ], + "semanticId": { + "type": "ExternalReference", + "keys": [ + { + "type": "GlobalReference", + "value": "0173-1#02-ABK127#002" + } + ] + }, + "supplementalSemanticIds": [ + { + "type": "ExternalReference", + "keys": [ + { + "type": "GlobalReference", + "value": "https://api.eclass-cdp.com/0173-1-02-ABK127-002" + } + ] + } + ], + "qualifiers": [ + { + "semanticId": { + "type": "ExternalReference", + "keys": [ + { + "type": "GlobalReference", + "value": "https://admin-shell.io/SubmodelTemplates/Cardinality/1/0" + } + ] + }, + "kind": "ConceptQualifier", + "type": "SMT/Cardinality", + "valueType": "xs:string", + "value": "ZeroToOne" + }, + { + "semanticId": { + "type": "ExternalReference", + "keys": [ + { + "type": "GlobalReference", + "value": "https://admin-shell.io/SubmodelTemplates/ExampleValue/1/0" + } + ] + }, + "kind": "ConceptQualifier", + "type": "ExampleValue", + "valueType": "xs:string", + "value": "docu_cecc_fullmanual_DE.jpg" + }, + { + "semanticId": { + "type": "ExternalReference", + "keys": [ + { + "type": "GlobalReference", + "value": "https://admin-shell.io/SubmodelTemplates/AllowedIdShort/1/0" + } + ] + }, + "kind": "ConceptQualifier", + "type": "AllowedIdShort", + "valueType": "xs:string", + "value": "PreviewFile[\\d{2,3}]" + } + ], + "value": "https://raw.githubusercontent.com/AAS-TwinEngine/AAS.TwinEngine.DataEngine/refs/heads/main/example/data/dummy_document.jpg", + "contentType": "image/jpeg", + "modelType": "File" + } + ], + "modelType": "SubmodelElementCollection" + } + ], + "modelType": "SubmodelElementList" + } + ], + "modelType": "SubmodelElementCollection" + }, + { + "description": [ + { + "language": "en", + "text": "This SubmodelElementCollection holds the information for a VDI 2770 Document entity" + } + ], + "semanticId": { + "type": "ExternalReference", + "keys": [ + { + "type": "GlobalReference", + "value": "0173-1#02-ABI500#003/0173-1#01-AHF579#003" + } + ] + }, + "supplementalSemanticIds": [ + { + "type": "ExternalReference", + "keys": [ + { + "type": "GlobalReference", + "value": "0173-1#02-ABI500#003~0/0173-1#01-AHF579#003" + }, + { + "type": "GlobalReference", + "value": "https://api.eclass-cdp.com/0173-1-02-ABI500-003/0173-1-01-AHF579-003" + } + ] + } + ], + "qualifiers": [ + { + "semanticId": { + "type": "ExternalReference", + "keys": [ + { + "type": "GlobalReference", + "value": "https://admin-shell.io/SubmodelTemplates/Cardinality/1/0" + } + ] + }, + "kind": "ConceptQualifier", + "type": "SMT/Cardinality", + "valueType": "xs:string", + "value": "OneToMany" + } + ], + "value": [ + { + "idShort": "DocumentIds", + "displayName": [ + { + "language": "en", + "text": "Document identifyers" + }, + { + "language": "de", + "text": "Dokumentidentifikatoren" + } + ], + "semanticId": { + "type": "ExternalReference", + "keys": [ + { + "type": "GlobalReference", + "value": "0173-1#02-ABI501#003" + } + ] + }, + "supplementalSemanticIds": [ + { + "type": "ExternalReference", + "keys": [ + { + "type": "GlobalReference", + "value": "https://api.eclass-cdp.com/0173-1-02-ABI501-003" + } + ] + } + ], + "qualifiers": [ + { + "semanticId": { + "type": "ExternalReference", + "keys": [ + { + "type": "GlobalReference", + "value": "https://admin-shell.io/SubmodelTemplates/Cardinality/1/0" + } + ] + }, + "kind": "ConceptQualifier", + "type": "SMT/Cardinality", + "valueType": "xs:string", + "value": "One" + } + ], + "orderRelevant": false, + "semanticIdListElement": { + "type": "ExternalReference", + "keys": [ + { + "type": "GlobalReference", + "value": "0173-1#02-ABI501#003/0173-1#01-AHF580#003" + } + ] + }, + "typeValueListElement": "SubmodelElementCollection", + "value": [ + { + "displayName": [ + { + "language": "en", + "text": "Document identificator" + }, + { + "language": "de", + "text": "Dokumentidentifikator" + } + ], + "description": [ + { + "language": "en", + "text": "This SubmodelElementCollection holds the information for a VDI 2770 Document entity" + } + ], + "semanticId": { + "type": "ExternalReference", + "keys": [ + { + "type": "GlobalReference", + "value": "0173-1#02-ABI501#003/0173-1#01-AHF580#003" + } + ] + }, + "supplementalSemanticIds": [ + { + "type": "ExternalReference", + "keys": [ + { + "type": "GlobalReference", + "value": "0173-1#02-ABI501#003~0/0173-1#01-AHF580#003" + }, + { + "type": "GlobalReference", + "value": "https://api.eclass-cdp.com/0173-1-02-ABI501-003/0173-1-01-AHF580-003" + } + ] + } + ], + "qualifiers": [ + { + "semanticId": { + "type": "ExternalReference", + "keys": [ + { + "type": "GlobalReference", + "value": "https://admin-shell.io/SubmodelTemplates/Cardinality/1/0" + } + ] + }, + "kind": "ConceptQualifier", + "type": "SMT/Cardinality", + "valueType": "xs:string", + "value": "OneToMany" + } + ], + "value": [ + { + "category": "PARAMETER", + "idShort": "DocumentDomainId", + "displayName": [ + { + "language": "en", + "text": "document domain identificator" + }, + { + "language": "de", + "text": "Document Domain Identifikator" + } + ], + "semanticId": { + "type": "ExternalReference", + "keys": [ + { + "type": "GlobalReference", + "value": "0173-1#02-ABH994#003" + } + ] + }, + "supplementalSemanticIds": [ + { + "type": "ExternalReference", + "keys": [ + { + "type": "GlobalReference", + "value": "https://api.eclass-cdp.com/0173-1-02-ABH994-003" + } + ] + } + ], + "qualifiers": [ + { + "semanticId": { + "type": "ExternalReference", + "keys": [ + { + "type": "GlobalReference", + "value": "https://admin-shell.io/SubmodelTemplates/Cardinality/1/0" + } + ] + }, + "kind": "ConceptQualifier", + "type": "SMT/Cardinality", + "valueType": "xs:string", + "value": "One" + }, + { + "semanticId": { + "type": "ExternalReference", + "keys": [ + { + "type": "GlobalReference", + "value": "https://admin-shell.io/SubmodelTemplates/ExampleValue/1/0" + } + ] + }, + "kind": "ConceptQualifier", + "type": "ExampleValue", + "valueType": "xs:string", + "value": "https://domain.com/..." + } + ], + "valueType": "xs:string", + "value": "c81a4f22-6d91-43bb-a812-03cd9e11d003", + "modelType": "Property" + }, + { + "category": "PARAMETER", + "idShort": "DocumentIdentifier", + "displayName": [ + { + "language": "en", + "text": "Document Identifyer" + }, + { + "language": "de", + "text": "Dokumentennummer" + } + ], + "semanticId": { + "type": "ExternalReference", + "keys": [ + { + "type": "GlobalReference", + "value": "0173-1#02-AAO099#004" + } + ] + }, + "supplementalSemanticIds": [ + { + "type": "ExternalReference", + "keys": [ + { + "type": "GlobalReference", + "value": "https://api.eclass-cdp.com/0173-1-02-AAO099-004" + } + ] + } + ], + "qualifiers": [ + { + "semanticId": { + "type": "ExternalReference", + "keys": [ + { + "type": "GlobalReference", + "value": "https://admin-shell.io/SubmodelTemplates/Cardinality/1/0" + } + ] + }, + "kind": "ConceptQualifier", + "type": "SMT/Cardinality", + "valueType": "xs:string", + "value": "One" + }, + { + "semanticId": { + "type": "ExternalReference", + "keys": [ + { + "type": "GlobalReference", + "value": "https://admin-shell.io/SubmodelTemplates/ExampleValue/1/0" + } + ] + }, + "kind": "ConceptQualifier", + "type": "ExampleValue", + "valueType": "xs:string", + "value": "XF90-884" + } + ], + "valueType": "xs:string", + "value": "MANUAL8F2Q7", + "modelType": "Property" + }, + { + "category": "PARAMETER", + "idShort": "DocumentIsPrimary", + "displayName": [ + { + "language": "en", + "text": "Document is primary" + }, + { + "language": "de", + "text": "Dokument ist primär" + } + ], + "semanticId": { + "type": "ExternalReference", + "keys": [ + { + "type": "GlobalReference", + "value": "0173-1#02-ABH995#003" + } + ] + }, + "supplementalSemanticIds": [ + { + "type": "ExternalReference", + "keys": [ + { + "type": "GlobalReference", + "value": "https://api.eclass-cdp.com/0173-1-02-ABH995-003" + } + ] + } + ], + "qualifiers": [ + { + "semanticId": { + "type": "ExternalReference", + "keys": [ + { + "type": "GlobalReference", + "value": "https://admin-shell.io/SubmodelTemplates/Cardinality/1/0" + } + ] + }, + "kind": "ConceptQualifier", + "type": "SMT/Cardinality", + "valueType": "xs:string", + "value": "ZeroToOne" + }, + { + "semanticId": { + "type": "ExternalReference", + "keys": [ + { + "type": "GlobalReference", + "value": "https://admin-shell.io/SubmodelTemplates/ExampleValue/1/0" + } + ] + }, + "kind": "ConceptQualifier", + "type": "ExampleValue", + "valueType": "xs:string", + "value": "true" + } + ], + "valueType": "xs:boolean", + "value": "True", + "modelType": "Property" + } + ], + "modelType": "SubmodelElementCollection" + } + ], + "modelType": "SubmodelElementList" + }, + { + "idShort": "DocumentClassifications", + "displayName": [ + { + "language": "en", + "text": "Document classifications" + }, + { + "language": "de", + "text": "Dokumentklassifikationen" + } + ], + "semanticId": { + "type": "ExternalReference", + "keys": [ + { + "type": "GlobalReference", + "value": "0173-1#02-ABI502#003" + } + ] + }, + "supplementalSemanticIds": [ + { + "type": "ExternalReference", + "keys": [ + { + "type": "GlobalReference", + "value": "https://api.eclass-cdp.com/0173-1-02-ABI502-003" + } + ] + } + ], + "qualifiers": [ + { + "semanticId": { + "type": "ExternalReference", + "keys": [ + { + "type": "GlobalReference", + "value": "https://admin-shell.io/SubmodelTemplates/Cardinality/1/0" + } + ] + }, + "kind": "ConceptQualifier", + "type": "SMT/Cardinality", + "valueType": "xs:string", + "value": "One" + } + ], + "orderRelevant": false, + "semanticIdListElement": { + "type": "ExternalReference", + "keys": [ + { + "type": "GlobalReference", + "value": "0173-1#02-ABI502#003/0173-1#01-AHF581#003" + } + ] + }, + "typeValueListElement": "SubmodelElementCollection", + "value": [ + { + "displayName": [ + { + "language": "en", + "text": "Document classification" + }, + { + "language": "de", + "text": "Dokumentklassifikation" + } + ], + "description": [ + { + "language": "en", + "text": "Set of information for describing the classification of the Document according to a ClassificationSystem" + } + ], + "semanticId": { + "type": "ExternalReference", + "keys": [ + { + "type": "GlobalReference", + "value": "0173-1#02-ABI502#003/0173-1#01-AHF581#003" + } + ] + }, + "supplementalSemanticIds": [ + { + "type": "ExternalReference", + "keys": [ + { + "type": "GlobalReference", + "value": "0173-1#02-ABI502#003~0/0173-1#01-AHF581#003" + }, + { + "type": "GlobalReference", + "value": "https://api.eclass-cdp.com/0173-1-02-ABI502-003/0173-1-01-AHF581-003" + } + ] + } + ], + "qualifiers": [ + { + "semanticId": { + "type": "ExternalReference", + "keys": [ + { + "type": "GlobalReference", + "value": "https://admin-shell.io/SubmodelTemplates/Cardinality/1/0" + } + ] + }, + "kind": "ConceptQualifier", + "type": "SMT/Cardinality", + "valueType": "xs:string", + "value": "OneToMany" + } + ], + "value": [ + { + "category": "PARAMETER", + "idShort": "ClassId", + "displayName": [ + { + "language": "en", + "text": "Class identificator" + }, + { + "language": "de", + "text": "Klassenidentifikator" + } + ], + "semanticId": { + "type": "ExternalReference", + "keys": [ + { + "type": "GlobalReference", + "value": "0173-1#02-ABH996#003" + } + ] + }, + "supplementalSemanticIds": [ + { + "type": "ExternalReference", + "keys": [ + { + "type": "GlobalReference", + "value": "https://api.eclass-cdp.com/0173-1-02-ABH996-003" + } + ] + } + ], + "qualifiers": [ + { + "semanticId": { + "type": "ExternalReference", + "keys": [ + { + "type": "GlobalReference", + "value": "https://admin-shell.io/SubmodelTemplates/Cardinality/1/0" + } + ] + }, + "kind": "ConceptQualifier", + "type": "SMT/Cardinality", + "valueType": "xs:string", + "value": "One" + }, + { + "semanticId": { + "type": "ExternalReference", + "keys": [ + { + "type": "GlobalReference", + "value": "https://admin-shell.io/SubmodelTemplates/ExampleValue/1/0" + } + ] + }, + "kind": "ConceptQualifier", + "type": "ExampleValue", + "valueType": "xs:string", + "value": "03-02" + } + ], + "valueType": "xs:string", + "value": "CLS-003", + "modelType": "Property" + }, + { + "category": "PARAMETER", + "idShort": "ClassificationSystem", + "displayName": [ + { + "language": "en", + "text": "Classification system" + }, + { + "language": "de", + "text": "Klassifizierungssystem" + } + ], + "semanticId": { + "type": "ExternalReference", + "keys": [ + { + "type": "GlobalReference", + "value": "0173-1#02-ABH997#003" + } + ] + }, + "supplementalSemanticIds": [ + { + "type": "ExternalReference", + "keys": [ + { + "type": "GlobalReference", + "value": "https://api.eclass-cdp.com/0173-1-02-ABH997-003" + } + ] + } + ], + "qualifiers": [ + { + "semanticId": { + "type": "ExternalReference", + "keys": [ + { + "type": "GlobalReference", + "value": "https://admin-shell.io/SubmodelTemplates/Cardinality/1/0" + } + ] + }, + "kind": "ConceptQualifier", + "type": "SMT/Cardinality", + "valueType": "xs:string", + "value": "One" + }, + { + "semanticId": { + "type": "ExternalReference", + "keys": [ + { + "type": "GlobalReference", + "value": "https://admin-shell.io/SubmodelTemplates/ExampleValue/1/0" + } + ] + }, + "kind": "ConceptQualifier", + "type": "ExampleValue", + "valueType": "xs:string", + "value": "VDI2770:2020" + } + ], + "valueType": "xs:string", + "value": "ECLASS-13.0", + "modelType": "Property" + }, + { + "category": "PARAMETER", + "idShort": "ClassName", + "displayName": [ + { + "language": "en", + "text": "Class Name" + }, + { + "language": "de", + "text": "Klassenname" + } + ], + "semanticId": { + "type": "ExternalReference", + "keys": [ + { + "type": "GlobalReference", + "value": "0173-1#02-ABJ219#002" + } + ] + }, + "supplementalSemanticIds": [ + { + "type": "ExternalReference", + "keys": [ + { + "type": "GlobalReference", + "value": "https://api.eclass-cdp.com/0173-1-02-ABJ219-002" + } + ] + } + ], + "qualifiers": [ + { + "semanticId": { + "type": "ExternalReference", + "keys": [ + { + "type": "GlobalReference", + "value": "https://admin-shell.io/SubmodelTemplates/Cardinality/1/0" + } + ] + }, + "kind": "ConceptQualifier", + "type": "SMT/Cardinality", + "valueType": "xs:string", + "value": "One" + }, + { + "semanticId": { + "type": "ExternalReference", + "keys": [ + { + "type": "GlobalReference", + "value": "https://admin-shell.io/SubmodelTemplates/ExampleValue/1/0" + } + ] + }, + "kind": "ConceptQualifier", + "type": "ExampleValue", + "valueType": "xs:string", + "value": "Operation@en" + } + ], + "modelType": "MultiLanguageProperty" + } + ], + "modelType": "SubmodelElementCollection" + } + ], + "modelType": "SubmodelElementList" + }, + { + "idShort": "DocumentVersions", + "displayName": [ + { + "language": "en", + "text": "Document versions" + }, + { + "language": "de", + "text": "Dokumentenversionen" + } + ], + "semanticId": { + "type": "ExternalReference", + "keys": [ + { + "type": "GlobalReference", + "value": "0173-1#02-ABI503#003" + } + ] + }, + "supplementalSemanticIds": [ + { + "type": "ExternalReference", + "keys": [ + { + "type": "GlobalReference", + "value": "https://api.eclass-cdp.com/0173-1-02-ABI503-003" + } + ] + } + ], + "qualifiers": [ + { + "semanticId": { + "type": "ExternalReference", + "keys": [ + { + "type": "GlobalReference", + "value": "https://admin-shell.io/SubmodelTemplates/Cardinality/1/0" + } + ] + }, + "kind": "ConceptQualifier", + "type": "SMT/Cardinality", + "valueType": "xs:string", + "value": "One" + } + ], + "orderRelevant": false, + "semanticIdListElement": { + "type": "ExternalReference", + "keys": [ + { + "type": "GlobalReference", + "value": "0173-1#02-ABI503#003/0173-1#01-AHF582#003" + } + ] + }, + "typeValueListElement": "SubmodelElementCollection", + "value": [ + { + "displayName": [ + { + "language": "en", + "text": "Document version" + }, + { + "language": "de", + "text": "Document version" + } + ], + "semanticId": { + "type": "ExternalReference", + "keys": [ + { + "type": "GlobalReference", + "value": "0173-1#02-ABI503#003/0173-1#01-AHF582#003" + } + ] + }, + "supplementalSemanticIds": [ + { + "type": "ExternalReference", + "keys": [ + { + "type": "GlobalReference", + "value": "0173-1#02-ABI503#003~0/0173-1#01-AHF582#003" + }, + { + "type": "GlobalReference", + "value": "https://api.eclass-cdp.com/0173-1-02-ABI503-003/0173-1-01-AHF582-003" + } + ] + } + ], + "qualifiers": [ + { + "semanticId": { + "type": "ExternalReference", + "keys": [ + { + "type": "GlobalReference", + "value": "https://admin-shell.io/SubmodelTemplates/Cardinality/1/0" + } + ] + }, + "kind": "ConceptQualifier", + "type": "SMT/Cardinality", + "valueType": "xs:string", + "value": "OneToMany" + } + ], + "value": [ + { + "idShort": "Language", + "displayName": [ + { + "language": "en", + "text": "Language" + }, + { + "language": "de", + "text": "Sprache" + } + ], + "semanticId": { + "type": "ExternalReference", + "keys": [ + { + "type": "GlobalReference", + "value": "0173-1#02-AAN468#008" + } + ] + }, + "supplementalSemanticIds": [ + { + "type": "ExternalReference", + "keys": [ + { + "type": "GlobalReference", + "value": "https://api.eclass-cdp.com/0173-1-02-AAN468-008" + } + ] + } + ], + "qualifiers": [ + { + "semanticId": { + "type": "ExternalReference", + "keys": [ + { + "type": "GlobalReference", + "value": "https://admin-shell.io/SubmodelTemplates/Cardinality/1/0" + } + ] + }, + "kind": "ConceptQualifier", + "type": "SMT/Cardinality", + "valueType": "xs:string", + "value": "One" + } + ], + "orderRelevant": false, + "typeValueListElement": "Property", + "valueTypeListElement": "xs:string", + "value": [ + { + "displayName": [ + { + "language": "en", + "text": "en (English)" + }, + { + "language": "de", + "text": "en (Englisch)" + } + ], + "semanticId": { + "type": "ExternalReference", + "keys": [ + { + "type": "GlobalReference", + "value": "0173-1#02-AAN468#008" + } + ] + }, + "qualifiers": [ + { + "semanticId": { + "type": "ExternalReference", + "keys": [ + { + "type": "GlobalReference", + "value": "https://admin-shell.io/SubmodelTemplates/Cardinality/1/0" + } + ] + }, + "kind": "ConceptQualifier", + "type": "SMT/Cardinality", + "valueType": "xs:string", + "value": "OneToMany" + }, + { + "semanticId": { + "type": "ExternalReference", + "keys": [ + { + "type": "GlobalReference", + "value": "https://admin-shell.io/SubmodelTemplates/ExampleValue/1/0" + } + ] + }, + "kind": "ConceptQualifier", + "type": "ExampleValue", + "valueType": "xs:string", + "value": "en" + } + ], + "valueType": "xs:string", + "value": "en", + "valueId": { + "type": "ExternalReference", + "keys": [ + { + "type": "GlobalReference", + "value": "0173-1#07-AAS045#003" + } + ] + }, + "modelType": "Property" + } + ], + "modelType": "SubmodelElementList" + }, + { + "idShort": "DigitalFiles", + "displayName": [ + { + "language": "en", + "text": "Digital files" + }, + { + "language": "de", + "text": "Digitale Dateien" + } + ], + "semanticId": { + "type": "ExternalReference", + "keys": [ + { + "type": "GlobalReference", + "value": "0173-1#02-ABK126#002" + } + ] + }, + "supplementalSemanticIds": [ + { + "type": "ExternalReference", + "keys": [ + { + "type": "GlobalReference", + "value": "https://api.eclass-cdp.com/0173-1-02-ABK126-002" + } + ] + } + ], + "qualifiers": [ + { + "semanticId": { + "type": "ExternalReference", + "keys": [ + { + "type": "GlobalReference", + "value": "https://admin-shell.io/SubmodelTemplates/ExampleValue/1/0" + } + ] + }, + "kind": "ConceptQualifier", + "type": "SMT/Cardinality", + "valueType": "xs:string", + "value": "One" + } + ], + "orderRelevant": false, + "typeValueListElement": "File", + "value": [ + { + "displayName": [ + { + "language": "en", + "text": "Name of the specific digital file@en" + }, + { + "language": "de", + "text": "Name der spezifischen digitalen Datei@de" + } + ], + "semanticId": { + "type": "ExternalReference", + "keys": [ + { + "type": "GlobalReference", + "value": "0173-1#02-ABK126#002" + } + ] + }, + "qualifiers": [ + { + "semanticId": { + "type": "ExternalReference", + "keys": [ + { + "type": "GlobalReference", + "value": "https://admin-shell.io/SubmodelTemplates/Cardinality/1/0" + } + ] + }, + "kind": "ConceptQualifier", + "type": "SMT/Cardinality", + "valueType": "xs:string", + "value": "OneToMany" + }, + { + "semanticId": { + "type": "ExternalReference", + "keys": [ + { + "type": "GlobalReference", + "value": "https://admin-shell.io/SubmodelTemplates/ExampleValue/1/0" + } + ] + }, + "kind": "ConceptQualifier", + "type": "ExampleValue", + "valueType": "xs:string", + "value": "docu_cecc_fullmanual_DE.PDF" + }, + { + "semanticId": { + "type": "ExternalReference", + "keys": [ + { + "type": "GlobalReference", + "value": "https://admin-shell.io/SubmodelTemplates/AllowedIdShort/1/0" + } + ] + }, + "kind": "ConceptQualifier", + "type": "AllowedIdShort", + "valueType": "xs:string", + "value": "DigitalFile[\\d{2,3}]" + } + ], + "value": "https://docs.google.com/viewer?url=https://raw.githubusercontent.com/AAS-TwinEngine/AAS.TwinEngine.DataEngine/refs/heads/main/example/data/dummy_document.pdf", + "contentType": "application/pdf", + "modelType": "File" + } + ], + "modelType": "SubmodelElementList" + }, + { + "category": "PARAMETER", + "idShort": "Version", + "displayName": [ + { + "language": "en", + "text": "Document version" + }, + { + "language": "de", + "text": "Dokumentenversion" + } + ], + "semanticId": { + "type": "ExternalReference", + "keys": [ + { + "type": "GlobalReference", + "value": "0173-1#02-AAP003#005" + } + ] + }, + "supplementalSemanticIds": [ + { + "type": "ExternalReference", + "keys": [ + { + "type": "GlobalReference", + "value": "https://api.eclass-cdp.com/0173-1-02-AAP003-005" + } + ] + } + ], + "qualifiers": [ + { + "semanticId": { + "type": "ExternalReference", + "keys": [ + { + "type": "GlobalReference", + "value": "https://admin-shell.io/SubmodelTemplates/Cardinality/1/0" + } + ] + }, + "kind": "ConceptQualifier", + "type": "SMT/Cardinality", + "valueType": "xs:string", + "value": "One" + }, + { + "semanticId": { + "type": "ExternalReference", + "keys": [ + { + "type": "GlobalReference", + "value": "https://admin-shell.io/SubmodelTemplates/ExampleValue/1/0" + } + ] + }, + "kind": "ConceptQualifier", + "type": "ExampleValue", + "valueType": "xs:string", + "value": "V1.2" + } + ], + "valueType": "xs:string", + "value": "2.1", + "modelType": "Property" + }, + { + "category": "PARAMETER", + "idShort": "StatusSetDate", + "displayName": [ + { + "language": "en", + "text": "Document status set date" + }, + { + "language": "de", + "text": "Datum der Einstellung des Dokumentenstatus" + } + ], + "semanticId": { + "type": "ExternalReference", + "keys": [ + { + "type": "GlobalReference", + "value": "0173-1#02-ABI000#003" + } + ] + }, + "supplementalSemanticIds": [ + { + "type": "ExternalReference", + "keys": [ + { + "type": "GlobalReference", + "value": "https://api.eclass-cdp.com/0173-1-02-ABI000-003" + } + ] + } + ], + "qualifiers": [ + { + "semanticId": { + "type": "ExternalReference", + "keys": [ + { + "type": "GlobalReference", + "value": "https://admin-shell.io/SubmodelTemplates/Cardinality/1/0" + } + ] + }, + "kind": "ConceptQualifier", + "type": "SMT/Cardinality", + "valueType": "xs:string", + "value": "One" + }, + { + "semanticId": { + "type": "ExternalReference", + "keys": [ + { + "type": "GlobalReference", + "value": "https://admin-shell.io/SubmodelTemplates/ExampleValue/1/0" + } + ] + }, + "kind": "ConceptQualifier", + "type": "ExampleValue", + "valueType": "xs:string", + "value": "2020-02-06" + } + ], + "valueType": "xs:date", + "value": "2026-01-01", + "modelType": "Property" + }, + { + "category": "PARAMETER", + "idShort": "StatusValue", + "displayName": [ + { + "language": "en", + "text": "Document status" + }, + { + "language": "de", + "text": "Dokumentstatus" + } + ], + "semanticId": { + "type": "ExternalReference", + "keys": [ + { + "type": "GlobalReference", + "value": "0173-1#02-ABI001#003" + } + ] + }, + "supplementalSemanticIds": [ + { + "type": "ExternalReference", + "keys": [ + { + "type": "GlobalReference", + "value": "https://api.eclass-cdp.com/0173-1-02-ABI001-003" + } + ] + } + ], + "qualifiers": [ + { + "semanticId": { + "type": "ExternalReference", + "keys": [ + { + "type": "GlobalReference", + "value": "https://admin-shell.io/SubmodelTemplates/Cardinality/1/0" + } + ] + }, + "kind": "ConceptQualifier", + "type": "SMT/Cardinality", + "valueType": "xs:string", + "value": "One" + }, + { + "semanticId": { + "type": "ExternalReference", + "keys": [ + { + "type": "GlobalReference", + "value": "https://admin-shell.io/SubmodelTemplates/ExampleValue/1/0" + } + ] + }, + "kind": "ConceptQualifier", + "type": "ExampleValue", + "valueType": "xs:string", + "value": "Released" + } + ], + "valueType": "xs:string", + "value": "Released", + "modelType": "Property" + }, + { + "category": "PARAMETER", + "idShort": "OrganizationShortName", + "displayName": [ + { + "language": "en", + "text": "Organization short name" + }, + { + "language": "de", + "text": "Kurzname der Organisation" + } + ], + "semanticId": { + "type": "ExternalReference", + "keys": [ + { + "type": "GlobalReference", + "value": "https://api.eclass-cdp.com/0173-1-02-ABI002-003" + } + ] + }, + "qualifiers": [ + { + "semanticId": { + "type": "ExternalReference", + "keys": [ + { + "type": "GlobalReference", + "value": "https://admin-shell.io/SubmodelTemplates/Cardinality/1/0" + } + ] + }, + "kind": "ConceptQualifier", + "type": "SMT/Cardinality", + "valueType": "xs:string", + "value": "One" + }, + { + "semanticId": { + "type": "ExternalReference", + "keys": [ + { + "type": "GlobalReference", + "value": "https://admin-shell.io/SubmodelTemplates/ExampleValue/1/0" + } + ] + }, + "kind": "ConceptQualifier", + "type": "ExampleValue", + "valueType": "xs:string", + "value": "Example company" + } + ], + "valueType": "xs:string", + "value": "M&M", + "modelType": "Property" + }, + { + "category": "PARAMETER", + "idShort": "OrganizationOfficialName", + "displayName": [ + { + "language": "en", + "text": "Organization official name" + }, + { + "language": "de", + "text": "Offizieller Name der Organisation" + } + ], + "semanticId": { + "type": "ExternalReference", + "keys": [ + { + "type": "GlobalReference", + "value": "0173-1#02-ABI004#003" + } + ] + }, + "supplementalSemanticIds": [ + { + "type": "ExternalReference", + "keys": [ + { + "type": "GlobalReference", + "value": "https://api.eclass-cdp.com/0173-1-02-ABI004-003" + } + ] + } + ], + "qualifiers": [ + { + "semanticId": { + "type": "ExternalReference", + "keys": [ + { + "type": "GlobalReference", + "value": "https://admin-shell.io/SubmodelTemplates/Cardinality/1/0" + } + ] + }, + "kind": "ConceptQualifier", + "type": "SMT/Cardinality", + "valueType": "xs:string", + "value": "One" + }, + { + "semanticId": { + "type": "ExternalReference", + "keys": [ + { + "type": "GlobalReference", + "value": "https://admin-shell.io/SubmodelTemplates/ExampleValue/1/0" + } + ] + }, + "kind": "ConceptQualifier", + "type": "ExampleValue", + "valueType": "xs:string", + "value": "Example company Ltd." + } + ], + "valueType": "xs:string", + "value": "M&M China", + "modelType": "Property" + }, + { + "category": "PARAMETER", + "idShort": "Title", + "displayName": [ + { + "language": "en", + "text": "Document title" + }, + { + "language": "de", + "text": "Dokumententitel" + } + ], + "semanticId": { + "type": "ExternalReference", + "keys": [ + { + "type": "GlobalReference", + "value": "0173-1#02-ABG940#003" + } + ] + }, + "supplementalSemanticIds": [ + { + "type": "ExternalReference", + "keys": [ + { + "type": "GlobalReference", + "value": "https://api.eclass-cdp.com/0173-1-02-ABG940-003" + } + ] + } + ], + "qualifiers": [ + { + "semanticId": { + "type": "ExternalReference", + "keys": [ + { + "type": "GlobalReference", + "value": "https://admin-shell.io/SubmodelTemplates/Cardinality/1/0" + } + ] + }, + "kind": "ConceptQualifier", + "type": "SMT/Cardinality", + "valueType": "xs:string", + "value": "One" + }, + { + "semanticId": { + "type": "ExternalReference", + "keys": [ + { + "type": "GlobalReference", + "value": "https://admin-shell.io/SubmodelTemplates/ExampleValue/1/0" + } + ] + }, + "kind": "ConceptQualifier", + "type": "ExampleValue", + "valueType": "xs:string", + "value": "Examplary title@en" + } + ], + "modelType": "MultiLanguageProperty" + }, + { + "category": "PARAMETER", + "idShort": "Subtitle", + "displayName": [ + { + "language": "en", + "text": "Subtitle" + }, + { + "language": "de", + "text": "Untertitel" + } + ], + "semanticId": { + "type": "ExternalReference", + "keys": [ + { + "type": "GlobalReference", + "value": "0173-1#02-ABH998#003" + } + ] + }, + "supplementalSemanticIds": [ + { + "type": "ExternalReference", + "keys": [ + { + "type": "GlobalReference", + "value": "https://api.eclass-cdp.com/0173-1-02-ABH998-003" + } + ] + } + ], + "qualifiers": [ + { + "semanticId": { + "type": "ExternalReference", + "keys": [ + { + "type": "GlobalReference", + "value": "https://admin-shell.io/SubmodelTemplates/Cardinality/1/0" + } + ] + }, + "kind": "ConceptQualifier", + "type": "SMT/Cardinality", + "valueType": "xs:string", + "value": "ZeroToOne" + }, + { + "semanticId": { + "type": "ExternalReference", + "keys": [ + { + "type": "GlobalReference", + "value": "https://admin-shell.io/SubmodelTemplates/ExampleValue/1/0" + } + ] + }, + "kind": "ConceptQualifier", + "type": "ExampleValue", + "valueType": "xs:string", + "value": "Examplary subtitle@en" + } + ], + "modelType": "MultiLanguageProperty" + }, + { + "category": "PARAMETER", + "idShort": "Description", + "displayName": [ + { + "language": "en", + "text": "Document description" + }, + { + "language": "de", + "text": "Dokumentenbeschreibung" + } + ], + "semanticId": { + "type": "ExternalReference", + "keys": [ + { + "type": "GlobalReference", + "value": "0173-1#02-AAN466#004" + } + ] + }, + "supplementalSemanticIds": [ + { + "type": "ExternalReference", + "keys": [ + { + "type": "GlobalReference", + "value": "https://api.eclass-cdp.com/0173-1-02-AAN466-004" + } + ] + } + ], + "qualifiers": [ + { + "semanticId": { + "type": "ExternalReference", + "keys": [ + { + "type": "GlobalReference", + "value": "https://admin-shell.io/SubmodelTemplates/Cardinality/1/0" + } + ] + }, + "kind": "ConceptQualifier", + "type": "SMT/Cardinality", + "valueType": "xs:string", + "value": "One" + }, + { + "semanticId": { + "type": "ExternalReference", + "keys": [ + { + "type": "GlobalReference", + "value": "https://admin-shell.io/SubmodelTemplates/ExampleValue/1/0" + } + ] + }, + "kind": "ConceptQualifier", + "type": "ExampleValue", + "valueType": "xs:string", + "value": "Abstract@en" + } + ], + "modelType": "MultiLanguageProperty" + }, + { + "category": "PARAMETER", + "idShort": "KeyWords", + "displayName": [ + { + "language": "en", + "text": "Keywords" + }, + { + "language": "de", + "text": "Stichworte" + } + ], + "semanticId": { + "type": "ExternalReference", + "keys": [ + { + "type": "GlobalReference", + "value": "0173-1#02-ABH999#003" + } + ] + }, + "supplementalSemanticIds": [ + { + "type": "ExternalReference", + "keys": [ + { + "type": "GlobalReference", + "value": "https://api.eclass-cdp.com/0173-1-02-ABH999-003" + } + ] + } + ], + "qualifiers": [ + { + "semanticId": { + "type": "ExternalReference", + "keys": [ + { + "type": "GlobalReference", + "value": "https://admin-shell.io/SubmodelTemplates/Cardinality/1/0" + } + ] + }, + "kind": "ConceptQualifier", + "type": "SMT/Cardinality", + "valueType": "xs:string", + "value": "ZeroToOne" + }, + { + "semanticId": { + "type": "ExternalReference", + "keys": [ + { + "type": "GlobalReference", + "value": "https://admin-shell.io/SubmodelTemplates/ExampleValue/1/0" + } + ] + }, + "kind": "ConceptQualifier", + "type": "ExampleValue", + "valueType": "xs:string", + "value": "Examplary keywords@en" + } + ], + "modelType": "MultiLanguageProperty" + }, + { + "category": "PARAMETER", + "idShort": "PreviewFile", + "displayName": [ + { + "language": "en", + "text": "Preview file" + }, + { + "language": "de", + "text": "Vorschaudatei" + } + ], + "semanticId": { + "type": "ExternalReference", + "keys": [ + { + "type": "GlobalReference", + "value": "0173-1#02-ABK127#002" + } + ] + }, + "supplementalSemanticIds": [ + { + "type": "ExternalReference", + "keys": [ + { + "type": "GlobalReference", + "value": "https://api.eclass-cdp.com/0173-1-02-ABK127-002" + } + ] + } + ], + "qualifiers": [ + { + "semanticId": { + "type": "ExternalReference", + "keys": [ + { + "type": "GlobalReference", + "value": "https://admin-shell.io/SubmodelTemplates/Cardinality/1/0" + } + ] + }, + "kind": "ConceptQualifier", + "type": "SMT/Cardinality", + "valueType": "xs:string", + "value": "ZeroToOne" + }, + { + "semanticId": { + "type": "ExternalReference", + "keys": [ + { + "type": "GlobalReference", + "value": "https://admin-shell.io/SubmodelTemplates/ExampleValue/1/0" + } + ] + }, + "kind": "ConceptQualifier", + "type": "ExampleValue", + "valueType": "xs:string", + "value": "docu_cecc_fullmanual_DE.jpg" + }, + { + "semanticId": { + "type": "ExternalReference", + "keys": [ + { + "type": "GlobalReference", + "value": "https://admin-shell.io/SubmodelTemplates/AllowedIdShort/1/0" + } + ] + }, + "kind": "ConceptQualifier", + "type": "AllowedIdShort", + "valueType": "xs:string", + "value": "PreviewFile[\\d{2,3}]" + } + ], + "value": "https://raw.githubusercontent.com/AAS-TwinEngine/AAS.TwinEngine.DataEngine/refs/heads/main/example/data/dummy_document.jpg", + "contentType": "image/jpeg", + "modelType": "File" + } + ], + "modelType": "SubmodelElementCollection" + } + ], + "modelType": "SubmodelElementList" + } + ], + "modelType": "SubmodelElementCollection" + }, + { + "description": [ + { + "language": "en", + "text": "This SubmodelElementCollection holds the information for a VDI 2770 Document entity" + } + ], + "semanticId": { + "type": "ExternalReference", + "keys": [ + { + "type": "GlobalReference", + "value": "0173-1#02-ABI500#003/0173-1#01-AHF579#003" + } + ] + }, + "supplementalSemanticIds": [ + { + "type": "ExternalReference", + "keys": [ + { + "type": "GlobalReference", + "value": "0173-1#02-ABI500#003~0/0173-1#01-AHF579#003" + }, + { + "type": "GlobalReference", + "value": "https://api.eclass-cdp.com/0173-1-02-ABI500-003/0173-1-01-AHF579-003" + } + ] + } + ], + "qualifiers": [ + { + "semanticId": { + "type": "ExternalReference", + "keys": [ + { + "type": "GlobalReference", + "value": "https://admin-shell.io/SubmodelTemplates/Cardinality/1/0" + } + ] + }, + "kind": "ConceptQualifier", + "type": "SMT/Cardinality", + "valueType": "xs:string", + "value": "OneToMany" + } + ], + "value": [ + { + "idShort": "DocumentIds", + "displayName": [ + { + "language": "en", + "text": "Document identifyers" + }, + { + "language": "de", + "text": "Dokumentidentifikatoren" + } + ], + "semanticId": { + "type": "ExternalReference", + "keys": [ + { + "type": "GlobalReference", + "value": "0173-1#02-ABI501#003" + } + ] + }, + "supplementalSemanticIds": [ + { + "type": "ExternalReference", + "keys": [ + { + "type": "GlobalReference", + "value": "https://api.eclass-cdp.com/0173-1-02-ABI501-003" + } + ] + } + ], + "qualifiers": [ + { + "semanticId": { + "type": "ExternalReference", + "keys": [ + { + "type": "GlobalReference", + "value": "https://admin-shell.io/SubmodelTemplates/Cardinality/1/0" + } + ] + }, + "kind": "ConceptQualifier", + "type": "SMT/Cardinality", + "valueType": "xs:string", + "value": "One" + } + ], + "orderRelevant": false, + "semanticIdListElement": { + "type": "ExternalReference", + "keys": [ + { + "type": "GlobalReference", + "value": "0173-1#02-ABI501#003/0173-1#01-AHF580#003" + } + ] + }, + "typeValueListElement": "SubmodelElementCollection", + "value": [ + { + "displayName": [ + { + "language": "en", + "text": "Document identificator" + }, + { + "language": "de", + "text": "Dokumentidentifikator" + } + ], + "description": [ + { + "language": "en", + "text": "This SubmodelElementCollection holds the information for a VDI 2770 Document entity" + } + ], + "semanticId": { + "type": "ExternalReference", + "keys": [ + { + "type": "GlobalReference", + "value": "0173-1#02-ABI501#003/0173-1#01-AHF580#003" + } + ] + }, + "supplementalSemanticIds": [ + { + "type": "ExternalReference", + "keys": [ + { + "type": "GlobalReference", + "value": "0173-1#02-ABI501#003~0/0173-1#01-AHF580#003" + }, + { + "type": "GlobalReference", + "value": "https://api.eclass-cdp.com/0173-1-02-ABI501-003/0173-1-01-AHF580-003" + } + ] + } + ], + "qualifiers": [ + { + "semanticId": { + "type": "ExternalReference", + "keys": [ + { + "type": "GlobalReference", + "value": "https://admin-shell.io/SubmodelTemplates/Cardinality/1/0" + } + ] + }, + "kind": "ConceptQualifier", + "type": "SMT/Cardinality", + "valueType": "xs:string", + "value": "OneToMany" + } + ], + "value": [ + { + "category": "PARAMETER", + "idShort": "DocumentDomainId", + "displayName": [ + { + "language": "en", + "text": "document domain identificator" + }, + { + "language": "de", + "text": "Document Domain Identifikator" + } + ], + "semanticId": { + "type": "ExternalReference", + "keys": [ + { + "type": "GlobalReference", + "value": "0173-1#02-ABH994#003" + } + ] + }, + "supplementalSemanticIds": [ + { + "type": "ExternalReference", + "keys": [ + { + "type": "GlobalReference", + "value": "https://api.eclass-cdp.com/0173-1-02-ABH994-003" + } + ] + } + ], + "qualifiers": [ + { + "semanticId": { + "type": "ExternalReference", + "keys": [ + { + "type": "GlobalReference", + "value": "https://admin-shell.io/SubmodelTemplates/Cardinality/1/0" + } + ] + }, + "kind": "ConceptQualifier", + "type": "SMT/Cardinality", + "valueType": "xs:string", + "value": "One" + }, + { + "semanticId": { + "type": "ExternalReference", + "keys": [ + { + "type": "GlobalReference", + "value": "https://admin-shell.io/SubmodelTemplates/ExampleValue/1/0" + } + ] + }, + "kind": "ConceptQualifier", + "type": "ExampleValue", + "valueType": "xs:string", + "value": "https://domain.com/..." + } + ], + "valueType": "xs:string", + "value": "a3f1c2b4-9d81-4e5a-b6f2-01ac9e11d002", + "modelType": "Property" + }, + { + "category": "PARAMETER", + "idShort": "DocumentIdentifier", + "displayName": [ + { + "language": "en", + "text": "Document Identifyer" + }, + { + "language": "de", + "text": "Dokumentennummer" + } + ], + "semanticId": { + "type": "ExternalReference", + "keys": [ + { + "type": "GlobalReference", + "value": "0173-1#02-AAO099#004" + } + ] + }, + "supplementalSemanticIds": [ + { + "type": "ExternalReference", + "keys": [ + { + "type": "GlobalReference", + "value": "https://api.eclass-cdp.com/0173-1-02-AAO099-004" + } + ] + } + ], + "qualifiers": [ + { + "semanticId": { + "type": "ExternalReference", + "keys": [ + { + "type": "GlobalReference", + "value": "https://admin-shell.io/SubmodelTemplates/Cardinality/1/0" + } + ] + }, + "kind": "ConceptQualifier", + "type": "SMT/Cardinality", + "valueType": "xs:string", + "value": "One" + }, + { + "semanticId": { + "type": "ExternalReference", + "keys": [ + { + "type": "GlobalReference", + "value": "https://admin-shell.io/SubmodelTemplates/ExampleValue/1/0" + } + ] + }, + "kind": "ConceptQualifier", + "type": "ExampleValue", + "valueType": "xs:string", + "value": "XF90-884" + } + ], + "valueType": "xs:string", + "value": "DOC2025A002", + "modelType": "Property" + }, + { + "category": "PARAMETER", + "idShort": "DocumentIsPrimary", + "displayName": [ + { + "language": "en", + "text": "Document is primary" + }, + { + "language": "de", + "text": "Dokument ist primär" + } + ], + "semanticId": { + "type": "ExternalReference", + "keys": [ + { + "type": "GlobalReference", + "value": "0173-1#02-ABH995#003" + } + ] + }, + "supplementalSemanticIds": [ + { + "type": "ExternalReference", + "keys": [ + { + "type": "GlobalReference", + "value": "https://api.eclass-cdp.com/0173-1-02-ABH995-003" + } + ] + } + ], + "qualifiers": [ + { + "semanticId": { + "type": "ExternalReference", + "keys": [ + { + "type": "GlobalReference", + "value": "https://admin-shell.io/SubmodelTemplates/Cardinality/1/0" + } + ] + }, + "kind": "ConceptQualifier", + "type": "SMT/Cardinality", + "valueType": "xs:string", + "value": "ZeroToOne" + }, + { + "semanticId": { + "type": "ExternalReference", + "keys": [ + { + "type": "GlobalReference", + "value": "https://admin-shell.io/SubmodelTemplates/ExampleValue/1/0" + } + ] + }, + "kind": "ConceptQualifier", + "type": "ExampleValue", + "valueType": "xs:string", + "value": "true" + } + ], + "valueType": "xs:boolean", + "value": "True", + "modelType": "Property" + } + ], + "modelType": "SubmodelElementCollection" + } + ], + "modelType": "SubmodelElementList" + }, + { + "idShort": "DocumentClassifications", + "displayName": [ + { + "language": "en", + "text": "Document classifications" + }, + { + "language": "de", + "text": "Dokumentklassifikationen" + } + ], + "semanticId": { + "type": "ExternalReference", + "keys": [ + { + "type": "GlobalReference", + "value": "0173-1#02-ABI502#003" + } + ] + }, + "supplementalSemanticIds": [ + { + "type": "ExternalReference", + "keys": [ + { + "type": "GlobalReference", + "value": "https://api.eclass-cdp.com/0173-1-02-ABI502-003" + } + ] + } + ], + "qualifiers": [ + { + "semanticId": { + "type": "ExternalReference", + "keys": [ + { + "type": "GlobalReference", + "value": "https://admin-shell.io/SubmodelTemplates/Cardinality/1/0" + } + ] + }, + "kind": "ConceptQualifier", + "type": "SMT/Cardinality", + "valueType": "xs:string", + "value": "One" + } + ], + "orderRelevant": false, + "semanticIdListElement": { + "type": "ExternalReference", + "keys": [ + { + "type": "GlobalReference", + "value": "0173-1#02-ABI502#003/0173-1#01-AHF581#003" + } + ] + }, + "typeValueListElement": "SubmodelElementCollection", + "value": [ + { + "displayName": [ + { + "language": "en", + "text": "Document classification" + }, + { + "language": "de", + "text": "Dokumentklassifikation" + } + ], + "description": [ + { + "language": "en", + "text": "Set of information for describing the classification of the Document according to a ClassificationSystem" + } + ], + "semanticId": { + "type": "ExternalReference", + "keys": [ + { + "type": "GlobalReference", + "value": "0173-1#02-ABI502#003/0173-1#01-AHF581#003" + } + ] + }, + "supplementalSemanticIds": [ + { + "type": "ExternalReference", + "keys": [ + { + "type": "GlobalReference", + "value": "0173-1#02-ABI502#003~0/0173-1#01-AHF581#003" + }, + { + "type": "GlobalReference", + "value": "https://api.eclass-cdp.com/0173-1-02-ABI502-003/0173-1-01-AHF581-003" + } + ] + } + ], + "qualifiers": [ + { + "semanticId": { + "type": "ExternalReference", + "keys": [ + { + "type": "GlobalReference", + "value": "https://admin-shell.io/SubmodelTemplates/Cardinality/1/0" + } + ] + }, + "kind": "ConceptQualifier", + "type": "SMT/Cardinality", + "valueType": "xs:string", + "value": "OneToMany" + } + ], + "value": [ + { + "category": "PARAMETER", + "idShort": "ClassId", + "displayName": [ + { + "language": "en", + "text": "Class identificator" + }, + { + "language": "de", + "text": "Klassenidentifikator" + } + ], + "semanticId": { + "type": "ExternalReference", + "keys": [ + { + "type": "GlobalReference", + "value": "0173-1#02-ABH996#003" + } + ] + }, + "supplementalSemanticIds": [ + { + "type": "ExternalReference", + "keys": [ + { + "type": "GlobalReference", + "value": "https://api.eclass-cdp.com/0173-1-02-ABH996-003" + } + ] + } + ], + "qualifiers": [ + { + "semanticId": { + "type": "ExternalReference", + "keys": [ + { + "type": "GlobalReference", + "value": "https://admin-shell.io/SubmodelTemplates/Cardinality/1/0" + } + ] + }, + "kind": "ConceptQualifier", + "type": "SMT/Cardinality", + "valueType": "xs:string", + "value": "One" + }, + { + "semanticId": { + "type": "ExternalReference", + "keys": [ + { + "type": "GlobalReference", + "value": "https://admin-shell.io/SubmodelTemplates/ExampleValue/1/0" + } + ] + }, + "kind": "ConceptQualifier", + "type": "ExampleValue", + "valueType": "xs:string", + "value": "03-02" + } + ], + "valueType": "xs:string", + "value": "CLS-004", + "modelType": "Property" + }, + { + "category": "PARAMETER", + "idShort": "ClassificationSystem", + "displayName": [ + { + "language": "en", + "text": "Classification system" + }, + { + "language": "de", + "text": "Klassifizierungssystem" + } + ], + "semanticId": { + "type": "ExternalReference", + "keys": [ + { + "type": "GlobalReference", + "value": "0173-1#02-ABH997#003" + } + ] + }, + "supplementalSemanticIds": [ + { + "type": "ExternalReference", + "keys": [ + { + "type": "GlobalReference", + "value": "https://api.eclass-cdp.com/0173-1-02-ABH997-003" + } + ] + } + ], + "qualifiers": [ + { + "semanticId": { + "type": "ExternalReference", + "keys": [ + { + "type": "GlobalReference", + "value": "https://admin-shell.io/SubmodelTemplates/Cardinality/1/0" + } + ] + }, + "kind": "ConceptQualifier", + "type": "SMT/Cardinality", + "valueType": "xs:string", + "value": "One" + }, + { + "semanticId": { + "type": "ExternalReference", + "keys": [ + { + "type": "GlobalReference", + "value": "https://admin-shell.io/SubmodelTemplates/ExampleValue/1/0" + } + ] + }, + "kind": "ConceptQualifier", + "type": "ExampleValue", + "valueType": "xs:string", + "value": "VDI2770:2020" + } + ], + "valueType": "xs:string", + "value": "UNSPSC", + "modelType": "Property" + }, + { + "category": "PARAMETER", + "idShort": "ClassName", + "displayName": [ + { + "language": "en", + "text": "Class Name" + }, + { + "language": "de", + "text": "Klassenname" + } + ], + "semanticId": { + "type": "ExternalReference", + "keys": [ + { + "type": "GlobalReference", + "value": "0173-1#02-ABJ219#002" + } + ] + }, + "supplementalSemanticIds": [ + { + "type": "ExternalReference", + "keys": [ + { + "type": "GlobalReference", + "value": "https://api.eclass-cdp.com/0173-1-02-ABJ219-002" + } + ] + } + ], + "qualifiers": [ + { + "semanticId": { + "type": "ExternalReference", + "keys": [ + { + "type": "GlobalReference", + "value": "https://admin-shell.io/SubmodelTemplates/Cardinality/1/0" + } + ] + }, + "kind": "ConceptQualifier", + "type": "SMT/Cardinality", + "valueType": "xs:string", + "value": "One" + }, + { + "semanticId": { + "type": "ExternalReference", + "keys": [ + { + "type": "GlobalReference", + "value": "https://admin-shell.io/SubmodelTemplates/ExampleValue/1/0" + } + ] + }, + "kind": "ConceptQualifier", + "type": "ExampleValue", + "valueType": "xs:string", + "value": "Operation@en" + } + ], + "modelType": "MultiLanguageProperty" + } + ], + "modelType": "SubmodelElementCollection" + } + ], + "modelType": "SubmodelElementList" + }, + { + "idShort": "DocumentVersions", + "displayName": [ + { + "language": "en", + "text": "Document versions" + }, + { + "language": "de", + "text": "Dokumentenversionen" + } + ], + "semanticId": { + "type": "ExternalReference", + "keys": [ + { + "type": "GlobalReference", + "value": "0173-1#02-ABI503#003" + } + ] + }, + "supplementalSemanticIds": [ + { + "type": "ExternalReference", + "keys": [ + { + "type": "GlobalReference", + "value": "https://api.eclass-cdp.com/0173-1-02-ABI503-003" + } + ] + } + ], + "qualifiers": [ + { + "semanticId": { + "type": "ExternalReference", + "keys": [ + { + "type": "GlobalReference", + "value": "https://admin-shell.io/SubmodelTemplates/Cardinality/1/0" + } + ] + }, + "kind": "ConceptQualifier", + "type": "SMT/Cardinality", + "valueType": "xs:string", + "value": "One" + } + ], + "orderRelevant": false, + "semanticIdListElement": { + "type": "ExternalReference", + "keys": [ + { + "type": "GlobalReference", + "value": "0173-1#02-ABI503#003/0173-1#01-AHF582#003" + } + ] + }, + "typeValueListElement": "SubmodelElementCollection", + "value": [ + { + "displayName": [ + { + "language": "en", + "text": "Document version" + }, + { + "language": "de", + "text": "Document version" + } + ], + "semanticId": { + "type": "ExternalReference", + "keys": [ + { + "type": "GlobalReference", + "value": "0173-1#02-ABI503#003/0173-1#01-AHF582#003" + } + ] + }, + "supplementalSemanticIds": [ + { + "type": "ExternalReference", + "keys": [ + { + "type": "GlobalReference", + "value": "0173-1#02-ABI503#003~0/0173-1#01-AHF582#003" + }, + { + "type": "GlobalReference", + "value": "https://api.eclass-cdp.com/0173-1-02-ABI503-003/0173-1-01-AHF582-003" + } + ] + } + ], + "qualifiers": [ + { + "semanticId": { + "type": "ExternalReference", + "keys": [ + { + "type": "GlobalReference", + "value": "https://admin-shell.io/SubmodelTemplates/Cardinality/1/0" + } + ] + }, + "kind": "ConceptQualifier", + "type": "SMT/Cardinality", + "valueType": "xs:string", + "value": "OneToMany" + } + ], + "value": [ + { + "idShort": "Language", + "displayName": [ + { + "language": "en", + "text": "Language" + }, + { + "language": "de", + "text": "Sprache" + } + ], + "semanticId": { + "type": "ExternalReference", + "keys": [ + { + "type": "GlobalReference", + "value": "0173-1#02-AAN468#008" + } + ] + }, + "supplementalSemanticIds": [ + { + "type": "ExternalReference", + "keys": [ + { + "type": "GlobalReference", + "value": "https://api.eclass-cdp.com/0173-1-02-AAN468-008" + } + ] + } + ], + "qualifiers": [ + { + "semanticId": { + "type": "ExternalReference", + "keys": [ + { + "type": "GlobalReference", + "value": "https://admin-shell.io/SubmodelTemplates/Cardinality/1/0" + } + ] + }, + "kind": "ConceptQualifier", + "type": "SMT/Cardinality", + "valueType": "xs:string", + "value": "One" + } + ], + "orderRelevant": false, + "typeValueListElement": "Property", + "valueTypeListElement": "xs:string", + "value": [ + { + "displayName": [ + { + "language": "en", + "text": "en (English)" + }, + { + "language": "de", + "text": "en (Englisch)" + } + ], + "semanticId": { + "type": "ExternalReference", + "keys": [ + { + "type": "GlobalReference", + "value": "0173-1#02-AAN468#008" + } + ] + }, + "qualifiers": [ + { + "semanticId": { + "type": "ExternalReference", + "keys": [ + { + "type": "GlobalReference", + "value": "https://admin-shell.io/SubmodelTemplates/Cardinality/1/0" + } + ] + }, + "kind": "ConceptQualifier", + "type": "SMT/Cardinality", + "valueType": "xs:string", + "value": "OneToMany" + }, + { + "semanticId": { + "type": "ExternalReference", + "keys": [ + { + "type": "GlobalReference", + "value": "https://admin-shell.io/SubmodelTemplates/ExampleValue/1/0" + } + ] + }, + "kind": "ConceptQualifier", + "type": "ExampleValue", + "valueType": "xs:string", + "value": "en" + } + ], + "valueType": "xs:string", + "value": "de", + "valueId": { + "type": "ExternalReference", + "keys": [ + { + "type": "GlobalReference", + "value": "0173-1#07-AAS045#003" + } + ] + }, + "modelType": "Property" + } + ], + "modelType": "SubmodelElementList" + }, + { + "idShort": "DigitalFiles", + "displayName": [ + { + "language": "en", + "text": "Digital files" + }, + { + "language": "de", + "text": "Digitale Dateien" + } + ], + "semanticId": { + "type": "ExternalReference", + "keys": [ + { + "type": "GlobalReference", + "value": "0173-1#02-ABK126#002" + } + ] + }, + "supplementalSemanticIds": [ + { + "type": "ExternalReference", + "keys": [ + { + "type": "GlobalReference", + "value": "https://api.eclass-cdp.com/0173-1-02-ABK126-002" + } + ] + } + ], + "qualifiers": [ + { + "semanticId": { + "type": "ExternalReference", + "keys": [ + { + "type": "GlobalReference", + "value": "https://admin-shell.io/SubmodelTemplates/ExampleValue/1/0" + } + ] + }, + "kind": "ConceptQualifier", + "type": "SMT/Cardinality", + "valueType": "xs:string", + "value": "One" + } + ], + "orderRelevant": false, + "typeValueListElement": "File", + "value": [ + { + "displayName": [ + { + "language": "en", + "text": "Name of the specific digital file@en" + }, + { + "language": "de", + "text": "Name der spezifischen digitalen Datei@de" + } + ], + "semanticId": { + "type": "ExternalReference", + "keys": [ + { + "type": "GlobalReference", + "value": "0173-1#02-ABK126#002" + } + ] + }, + "qualifiers": [ + { + "semanticId": { + "type": "ExternalReference", + "keys": [ + { + "type": "GlobalReference", + "value": "https://admin-shell.io/SubmodelTemplates/Cardinality/1/0" + } + ] + }, + "kind": "ConceptQualifier", + "type": "SMT/Cardinality", + "valueType": "xs:string", + "value": "OneToMany" + }, + { + "semanticId": { + "type": "ExternalReference", + "keys": [ + { + "type": "GlobalReference", + "value": "https://admin-shell.io/SubmodelTemplates/ExampleValue/1/0" + } + ] + }, + "kind": "ConceptQualifier", + "type": "ExampleValue", + "valueType": "xs:string", + "value": "docu_cecc_fullmanual_DE.PDF" + }, + { + "semanticId": { + "type": "ExternalReference", + "keys": [ + { + "type": "GlobalReference", + "value": "https://admin-shell.io/SubmodelTemplates/AllowedIdShort/1/0" + } + ] + }, + "kind": "ConceptQualifier", + "type": "AllowedIdShort", + "valueType": "xs:string", + "value": "DigitalFile[\\d{2,3}]" + } + ], + "value": "https://docs.google.com/viewer?url=https://raw.githubusercontent.com/AAS-TwinEngine/AAS.TwinEngine.DataEngine/refs/heads/main/example/data/dummy_document.pdf", + "contentType": "application/pdf", + "modelType": "File" + } + ], + "modelType": "SubmodelElementList" + }, + { + "category": "PARAMETER", + "idShort": "Version", + "displayName": [ + { + "language": "en", + "text": "Document version" + }, + { + "language": "de", + "text": "Dokumentenversion" + } + ], + "semanticId": { + "type": "ExternalReference", + "keys": [ + { + "type": "GlobalReference", + "value": "0173-1#02-AAP003#005" + } + ] + }, + "supplementalSemanticIds": [ + { + "type": "ExternalReference", + "keys": [ + { + "type": "GlobalReference", + "value": "https://api.eclass-cdp.com/0173-1-02-AAP003-005" + } + ] + } + ], + "qualifiers": [ + { + "semanticId": { + "type": "ExternalReference", + "keys": [ + { + "type": "GlobalReference", + "value": "https://admin-shell.io/SubmodelTemplates/Cardinality/1/0" + } + ] + }, + "kind": "ConceptQualifier", + "type": "SMT/Cardinality", + "valueType": "xs:string", + "value": "One" + }, + { + "semanticId": { + "type": "ExternalReference", + "keys": [ + { + "type": "GlobalReference", + "value": "https://admin-shell.io/SubmodelTemplates/ExampleValue/1/0" + } + ] + }, + "kind": "ConceptQualifier", + "type": "ExampleValue", + "valueType": "xs:string", + "value": "V1.2" + } + ], + "valueType": "xs:string", + "value": "2.3", + "modelType": "Property" + }, + { + "category": "PARAMETER", + "idShort": "StatusSetDate", + "displayName": [ + { + "language": "en", + "text": "Document status set date" + }, + { + "language": "de", + "text": "Datum der Einstellung des Dokumentenstatus" + } + ], + "semanticId": { + "type": "ExternalReference", + "keys": [ + { + "type": "GlobalReference", + "value": "0173-1#02-ABI000#003" + } + ] + }, + "supplementalSemanticIds": [ + { + "type": "ExternalReference", + "keys": [ + { + "type": "GlobalReference", + "value": "https://api.eclass-cdp.com/0173-1-02-ABI000-003" + } + ] + } + ], + "qualifiers": [ + { + "semanticId": { + "type": "ExternalReference", + "keys": [ + { + "type": "GlobalReference", + "value": "https://admin-shell.io/SubmodelTemplates/Cardinality/1/0" + } + ] + }, + "kind": "ConceptQualifier", + "type": "SMT/Cardinality", + "valueType": "xs:string", + "value": "One" + }, + { + "semanticId": { + "type": "ExternalReference", + "keys": [ + { + "type": "GlobalReference", + "value": "https://admin-shell.io/SubmodelTemplates/ExampleValue/1/0" + } + ] + }, + "kind": "ConceptQualifier", + "type": "ExampleValue", + "valueType": "xs:string", + "value": "2020-02-06" + } + ], + "valueType": "xs:date", + "value": "2025-10-10", + "modelType": "Property" + }, + { + "category": "PARAMETER", + "idShort": "StatusValue", + "displayName": [ + { + "language": "en", + "text": "Document status" + }, + { + "language": "de", + "text": "Dokumentstatus" + } + ], + "semanticId": { + "type": "ExternalReference", + "keys": [ + { + "type": "GlobalReference", + "value": "0173-1#02-ABI001#003" + } + ] + }, + "supplementalSemanticIds": [ + { + "type": "ExternalReference", + "keys": [ + { + "type": "GlobalReference", + "value": "https://api.eclass-cdp.com/0173-1-02-ABI001-003" + } + ] + } + ], + "qualifiers": [ + { + "semanticId": { + "type": "ExternalReference", + "keys": [ + { + "type": "GlobalReference", + "value": "https://admin-shell.io/SubmodelTemplates/Cardinality/1/0" + } + ] + }, + "kind": "ConceptQualifier", + "type": "SMT/Cardinality", + "valueType": "xs:string", + "value": "One" + }, + { + "semanticId": { + "type": "ExternalReference", + "keys": [ + { + "type": "GlobalReference", + "value": "https://admin-shell.io/SubmodelTemplates/ExampleValue/1/0" + } + ] + }, + "kind": "ConceptQualifier", + "type": "ExampleValue", + "valueType": "xs:string", + "value": "Released" + } + ], + "valueType": "xs:string", + "value": "InReview", + "modelType": "Property" + }, + { + "category": "PARAMETER", + "idShort": "OrganizationShortName", + "displayName": [ + { + "language": "en", + "text": "Organization short name" + }, + { + "language": "de", + "text": "Kurzname der Organisation" + } + ], + "semanticId": { + "type": "ExternalReference", + "keys": [ + { + "type": "GlobalReference", + "value": "https://api.eclass-cdp.com/0173-1-02-ABI002-003" + } + ] + }, + "qualifiers": [ + { + "semanticId": { + "type": "ExternalReference", + "keys": [ + { + "type": "GlobalReference", + "value": "https://admin-shell.io/SubmodelTemplates/Cardinality/1/0" + } + ] + }, + "kind": "ConceptQualifier", + "type": "SMT/Cardinality", + "valueType": "xs:string", + "value": "One" + }, + { + "semanticId": { + "type": "ExternalReference", + "keys": [ + { + "type": "GlobalReference", + "value": "https://admin-shell.io/SubmodelTemplates/ExampleValue/1/0" + } + ] + }, + "kind": "ConceptQualifier", + "type": "ExampleValue", + "valueType": "xs:string", + "value": "Example company" + } + ], + "valueType": "xs:string", + "value": "M&M", + "modelType": "Property" + }, + { + "category": "PARAMETER", + "idShort": "OrganizationOfficialName", + "displayName": [ + { + "language": "en", + "text": "Organization official name" + }, + { + "language": "de", + "text": "Offizieller Name der Organisation" + } + ], + "semanticId": { + "type": "ExternalReference", + "keys": [ + { + "type": "GlobalReference", + "value": "0173-1#02-ABI004#003" + } + ] + }, + "supplementalSemanticIds": [ + { + "type": "ExternalReference", + "keys": [ + { + "type": "GlobalReference", + "value": "https://api.eclass-cdp.com/0173-1-02-ABI004-003" + } + ] + } + ], + "qualifiers": [ + { + "semanticId": { + "type": "ExternalReference", + "keys": [ + { + "type": "GlobalReference", + "value": "https://admin-shell.io/SubmodelTemplates/Cardinality/1/0" + } + ] + }, + "kind": "ConceptQualifier", + "type": "SMT/Cardinality", + "valueType": "xs:string", + "value": "One" + }, + { + "semanticId": { + "type": "ExternalReference", + "keys": [ + { + "type": "GlobalReference", + "value": "https://admin-shell.io/SubmodelTemplates/ExampleValue/1/0" + } + ] + }, + "kind": "ConceptQualifier", + "type": "ExampleValue", + "valueType": "xs:string", + "value": "Example company Ltd." + } + ], + "valueType": "xs:string", + "value": "M&M Germany", + "modelType": "Property" + }, + { + "category": "PARAMETER", + "idShort": "Title", + "displayName": [ + { + "language": "en", + "text": "Document title" + }, + { + "language": "de", + "text": "Dokumententitel" + } + ], + "semanticId": { + "type": "ExternalReference", + "keys": [ + { + "type": "GlobalReference", + "value": "0173-1#02-ABG940#003" + } + ] + }, + "supplementalSemanticIds": [ + { + "type": "ExternalReference", + "keys": [ + { + "type": "GlobalReference", + "value": "https://api.eclass-cdp.com/0173-1-02-ABG940-003" + } + ] + } + ], + "qualifiers": [ + { + "semanticId": { + "type": "ExternalReference", + "keys": [ + { + "type": "GlobalReference", + "value": "https://admin-shell.io/SubmodelTemplates/Cardinality/1/0" + } + ] + }, + "kind": "ConceptQualifier", + "type": "SMT/Cardinality", + "valueType": "xs:string", + "value": "One" + }, + { + "semanticId": { + "type": "ExternalReference", + "keys": [ + { + "type": "GlobalReference", + "value": "https://admin-shell.io/SubmodelTemplates/ExampleValue/1/0" + } + ] + }, + "kind": "ConceptQualifier", + "type": "ExampleValue", + "valueType": "xs:string", + "value": "Examplary title@en" + } + ], + "modelType": "MultiLanguageProperty" + }, + { + "category": "PARAMETER", + "idShort": "Subtitle", + "displayName": [ + { + "language": "en", + "text": "Subtitle" + }, + { + "language": "de", + "text": "Untertitel" + } + ], + "semanticId": { + "type": "ExternalReference", + "keys": [ + { + "type": "GlobalReference", + "value": "0173-1#02-ABH998#003" + } + ] + }, + "supplementalSemanticIds": [ + { + "type": "ExternalReference", + "keys": [ + { + "type": "GlobalReference", + "value": "https://api.eclass-cdp.com/0173-1-02-ABH998-003" + } + ] + } + ], + "qualifiers": [ + { + "semanticId": { + "type": "ExternalReference", + "keys": [ + { + "type": "GlobalReference", + "value": "https://admin-shell.io/SubmodelTemplates/Cardinality/1/0" + } + ] + }, + "kind": "ConceptQualifier", + "type": "SMT/Cardinality", + "valueType": "xs:string", + "value": "ZeroToOne" + }, + { + "semanticId": { + "type": "ExternalReference", + "keys": [ + { + "type": "GlobalReference", + "value": "https://admin-shell.io/SubmodelTemplates/ExampleValue/1/0" + } + ] + }, + "kind": "ConceptQualifier", + "type": "ExampleValue", + "valueType": "xs:string", + "value": "Examplary subtitle@en" + } + ], + "modelType": "MultiLanguageProperty" + }, + { + "category": "PARAMETER", + "idShort": "Description", + "displayName": [ + { + "language": "en", + "text": "Document description" + }, + { + "language": "de", + "text": "Dokumentenbeschreibung" + } + ], + "semanticId": { + "type": "ExternalReference", + "keys": [ + { + "type": "GlobalReference", + "value": "0173-1#02-AAN466#004" + } + ] + }, + "supplementalSemanticIds": [ + { + "type": "ExternalReference", + "keys": [ + { + "type": "GlobalReference", + "value": "https://api.eclass-cdp.com/0173-1-02-AAN466-004" + } + ] + } + ], + "qualifiers": [ + { + "semanticId": { + "type": "ExternalReference", + "keys": [ + { + "type": "GlobalReference", + "value": "https://admin-shell.io/SubmodelTemplates/Cardinality/1/0" + } + ] + }, + "kind": "ConceptQualifier", + "type": "SMT/Cardinality", + "valueType": "xs:string", + "value": "One" + }, + { + "semanticId": { + "type": "ExternalReference", + "keys": [ + { + "type": "GlobalReference", + "value": "https://admin-shell.io/SubmodelTemplates/ExampleValue/1/0" + } + ] + }, + "kind": "ConceptQualifier", + "type": "ExampleValue", + "valueType": "xs:string", + "value": "Abstract@en" + } + ], + "modelType": "MultiLanguageProperty" + }, + { + "category": "PARAMETER", + "idShort": "KeyWords", + "displayName": [ + { + "language": "en", + "text": "Keywords" + }, + { + "language": "de", + "text": "Stichworte" + } + ], + "semanticId": { + "type": "ExternalReference", + "keys": [ + { + "type": "GlobalReference", + "value": "0173-1#02-ABH999#003" + } + ] + }, + "supplementalSemanticIds": [ + { + "type": "ExternalReference", + "keys": [ + { + "type": "GlobalReference", + "value": "https://api.eclass-cdp.com/0173-1-02-ABH999-003" + } + ] + } + ], + "qualifiers": [ + { + "semanticId": { + "type": "ExternalReference", + "keys": [ + { + "type": "GlobalReference", + "value": "https://admin-shell.io/SubmodelTemplates/Cardinality/1/0" + } + ] + }, + "kind": "ConceptQualifier", + "type": "SMT/Cardinality", + "valueType": "xs:string", + "value": "ZeroToOne" + }, + { + "semanticId": { + "type": "ExternalReference", + "keys": [ + { + "type": "GlobalReference", + "value": "https://admin-shell.io/SubmodelTemplates/ExampleValue/1/0" + } + ] + }, + "kind": "ConceptQualifier", + "type": "ExampleValue", + "valueType": "xs:string", + "value": "Examplary keywords@en" + } + ], + "modelType": "MultiLanguageProperty" + }, + { + "category": "PARAMETER", + "idShort": "PreviewFile", + "displayName": [ + { + "language": "en", + "text": "Preview file" + }, + { + "language": "de", + "text": "Vorschaudatei" + } + ], + "semanticId": { + "type": "ExternalReference", + "keys": [ + { + "type": "GlobalReference", + "value": "0173-1#02-ABK127#002" + } + ] + }, + "supplementalSemanticIds": [ + { + "type": "ExternalReference", + "keys": [ + { + "type": "GlobalReference", + "value": "https://api.eclass-cdp.com/0173-1-02-ABK127-002" + } + ] + } + ], + "qualifiers": [ + { + "semanticId": { + "type": "ExternalReference", + "keys": [ + { + "type": "GlobalReference", + "value": "https://admin-shell.io/SubmodelTemplates/Cardinality/1/0" + } + ] + }, + "kind": "ConceptQualifier", + "type": "SMT/Cardinality", + "valueType": "xs:string", + "value": "ZeroToOne" + }, + { + "semanticId": { + "type": "ExternalReference", + "keys": [ + { + "type": "GlobalReference", + "value": "https://admin-shell.io/SubmodelTemplates/ExampleValue/1/0" + } + ] + }, + "kind": "ConceptQualifier", + "type": "ExampleValue", + "valueType": "xs:string", + "value": "docu_cecc_fullmanual_DE.jpg" + }, + { + "semanticId": { + "type": "ExternalReference", + "keys": [ + { + "type": "GlobalReference", + "value": "https://admin-shell.io/SubmodelTemplates/AllowedIdShort/1/0" + } + ] + }, + "kind": "ConceptQualifier", + "type": "AllowedIdShort", + "valueType": "xs:string", + "value": "PreviewFile[\\d{2,3}]" + } + ], + "value": "https://raw.githubusercontent.com/AAS-TwinEngine/AAS.TwinEngine.DataEngine/refs/heads/main/example/data/dummy_document.jpg", + "contentType": "image/jpeg", + "modelType": "File" + } + ], + "modelType": "SubmodelElementCollection" + } + ], + "modelType": "SubmodelElementList" + } + ], + "modelType": "SubmodelElementCollection" + } + ], + "modelType": "SubmodelElementList" + }, + { + "idShort": "Entities", + "semanticId": { + "type": "ExternalReference", + "keys": [ + { + "type": "GlobalReference", + "value": "https://admin-shell.io/vdi/2770/1/0/EntitiesForDocumentation" + } + ] + }, + "qualifiers": [ + { + "semanticId": { + "type": "ExternalReference", + "keys": [ + { + "type": "GlobalReference", + "value": "https://admin-shell.io/SubmodelTemplates/Cardinality/1/0" + } + ] + }, + "kind": "ConceptQualifier", + "type": "SMT/Cardinality", + "valueType": "xs:string", + "value": "ZeroToOne" + } + ], + "orderRelevant": false, + "typeValueListElement": "Entity", + "modelType": "SubmodelElementList" + } + ] +} diff --git a/source/AAS.TwinEngine.Plugin.TestPlugin.PlaywrightTests/AasRepository/TestData/GetAllSubmodelElementsByAasId_WithCursor_Expected.json b/source/AAS.TwinEngine.Plugin.TestPlugin.PlaywrightTests/AasRepository/TestData/GetAllSubmodelElementsByAasId_WithCursor_Expected.json new file mode 100644 index 00000000..0e50c0c3 --- /dev/null +++ b/source/AAS.TwinEngine.Plugin.TestPlugin.PlaywrightTests/AasRepository/TestData/GetAllSubmodelElementsByAasId_WithCursor_Expected.json @@ -0,0 +1,39 @@ +{ + "paging_metadata": { + "cursor": null + }, + "result": [ + { + "idShort": "Entities", + "semanticId": { + "type": "ExternalReference", + "keys": [ + { + "type": "GlobalReference", + "value": "https://admin-shell.io/vdi/2770/1/0/EntitiesForDocumentation" + } + ] + }, + "qualifiers": [ + { + "semanticId": { + "type": "ExternalReference", + "keys": [ + { + "type": "GlobalReference", + "value": "https://admin-shell.io/SubmodelTemplates/Cardinality/1/0" + } + ] + }, + "kind": "ConceptQualifier", + "type": "SMT/Cardinality", + "valueType": "xs:string", + "value": "ZeroToOne" + } + ], + "orderRelevant": false, + "typeValueListElement": "Entity", + "modelType": "SubmodelElementList" + } + ] +} diff --git a/source/AAS.TwinEngine.Plugin.TestPlugin.PlaywrightTests/AasRepository/TestData/GetSubmodelByAasId_Expected.json b/source/AAS.TwinEngine.Plugin.TestPlugin.PlaywrightTests/AasRepository/TestData/GetSubmodelByAasId_Expected.json new file mode 100644 index 00000000..4cfeff87 --- /dev/null +++ b/source/AAS.TwinEngine.Plugin.TestPlugin.PlaywrightTests/AasRepository/TestData/GetSubmodelByAasId_Expected.json @@ -0,0 +1,1603 @@ +{ + "idShort": "CustomSubmodel", + "description": [ + { + "language": "en", + "text": "The Submodel HierarchicalStructures identified by its semanticId. The Submodel idShort can be picked freely." + } + ], + "id": "https://mm-software.com/submodel/000-001/CustomSubmodel", + "kind": "Instance", + "semanticId": { + "type": "ExternalReference", + "keys": [ + { + "type": "GlobalReference", + "value": "https://admin-shell.io/idta/CustomSubmodel/Submodel/Template/0/1" + } + ] + }, + "qualifiers": [ + { + "semanticId": { + "type": "ExternalReference", + "keys": [ + { + "type": "GlobalReference", + "value": "https://admin-shell.io/SubmodelTemplates/Cardinality/1/0" + } + ] + }, + "kind": "TemplateQualifier", + "type": "SMT/Cardinality", + "valueType": "xs:string", + "value": "ZeroToOne" + }, + { + "kind": "TemplateQualifier", + "type": "EditIdShort", + "valueType": "xs:string", + "value": "True" + } + ], + "submodelElements": [ + { + "idShort": "EntryNode", + "description": [ + { + "language": "en", + "text": "Base entry point for the Entity tree in this Submodel, this must be a Self-managed Entity reflecting the Assets administrated in the AAS this Submodel is part of." + } + ], + "semanticId": { + "type": "ExternalReference", + "keys": [ + { + "type": "GlobalReference", + "value": "https://admin-shell.io/idta/HierarchicalStructures/EntryNode/1/0" + } + ] + }, + "qualifiers": [ + { + "semanticId": { + "type": "ExternalReference", + "keys": [ + { + "type": "GlobalReference", + "value": "https://admin-shell.io/SubmodelTemplates/Cardinality/1/0" + } + ] + }, + "kind": "TemplateQualifier", + "type": "SMT/Cardinality", + "valueType": "xs:string", + "value": "One" + }, + { + "kind": "TemplateQualifier", + "type": "EditIdShort", + "valueType": "xs:string", + "value": "True" + } + ], + "statements": [ + { + "idShort": "Node", + "description": [ + { + "language": "en", + "text": "Base entry point for the Entity tree in this Submodel, this must be a Self-managed Entity reflecting the Assets administrated in the Asset Administration Shell this Submodel is part of. The idShort of the EntryNode can be picked freely and may reflect a name of the asset." + } + ], + "semanticId": { + "type": "ExternalReference", + "keys": [ + { + "type": "GlobalReference", + "value": "https://admin-shell.io/idta/HierarchicalStructures/Node/1/0" + } + ] + }, + "qualifiers": [ + { + "semanticId": { + "type": "ExternalReference", + "keys": [ + { + "type": "GlobalReference", + "value": "https://admin-shell.io/SubmodelTemplates/Cardinality/1/0" + } + ] + }, + "kind": "TemplateQualifier", + "type": "SMT/Cardinality", + "valueType": "xs:string", + "value": "OneToMany" + }, + { + "kind": "TemplateQualifier", + "type": "EditIdShort", + "valueType": "xs:string", + "value": "True" + } + ], + "statements": [ + { + "idShort": "Node", + "description": [ + { + "language": "en", + "text": "Can be a Co-managed or Self-managed entity. A Node reflects an element in the hierarchical model is set into relation with one or more defined relations. The name of a node can be picked freely but it must be unique in its hierarchical (sub-)level." + } + ], + "semanticId": { + "type": "ExternalReference", + "keys": [ + { + "type": "GlobalReference", + "value": "https://admin-shell.io/idta/HierarchicalStructures/ChildNode/1/0" + } + ] + }, + "qualifiers": [ + { + "semanticId": { + "type": "ExternalReference", + "keys": [ + { + "type": "GlobalReference", + "value": "https://admin-shell.io/SubmodelTemplates/Cardinality/1/0" + } + ] + }, + "kind": "TemplateQualifier", + "type": "SMT/Cardinality", + "valueType": "xs:string", + "value": "ZeroToMany" + }, + { + "kind": "TemplateQualifier", + "type": "EditIdShort", + "valueType": "xs:string", + "value": "True" + } + ], + "entityType": "SelfManagedEntity", + "globalAssetId": "https://mm-software.com/ids/assets/000-003", + "modelType": "Entity" + }, + { + "idShort": "SameAs", + "description": [ + { + "language": "en", + "text": "Reference between two Entities in the same Submodel or across Submodels." + } + ], + "semanticId": { + "type": "ExternalReference", + "keys": [ + { + "type": "GlobalReference", + "value": "https://admin-shell.io/idta/HierarchicalStructures/SameAs/1/0" + } + ] + }, + "qualifiers": [ + { + "semanticId": { + "type": "ExternalReference", + "keys": [ + { + "type": "GlobalReference", + "value": "https://admin-shell.io/SubmodelTemplates/Cardinality/1/0" + } + ] + }, + "kind": "TemplateQualifier", + "type": "SMT/Cardinality", + "valueType": "xs:string", + "value": "ZeroToMany" + }, + { + "kind": "TemplateQualifier", + "type": "EditIdShort", + "valueType": "xs:string", + "value": "True" + } + ], + "first": { + "type": "ModelReference", + "keys": [] + }, + "second": { + "type": "ModelReference", + "keys": [] + }, + "modelType": "RelationshipElement" + }, + { + "idShort": "IsPartOf", + "description": [ + { + "language": "en", + "text": "Modeling of logical connections between components and sub-components. Either this or \"HasPart\" must be used, not both." + } + ], + "semanticId": { + "type": "ExternalReference", + "keys": [ + { + "type": "GlobalReference", + "value": "https://admin-shell.io/idta/HierarchicalStructures/IsPartOf/1/0" + } + ] + }, + "qualifiers": [ + { + "semanticId": { + "type": "ExternalReference", + "keys": [ + { + "type": "GlobalReference", + "value": "https://admin-shell.io/SubmodelTemplates/Cardinality/1/0" + } + ] + }, + "kind": "TemplateQualifier", + "type": "SMT/Cardinality", + "valueType": "xs:string", + "value": "ZeroToMany" + }, + { + "kind": "TemplateQualifier", + "type": "EditIdShort", + "valueType": "xs:string", + "value": "True" + } + ], + "first": { + "type": "ModelReference", + "keys": [] + }, + "second": { + "type": "ModelReference", + "keys": [] + }, + "modelType": "RelationshipElement" + }, + { + "idShort": "HasPart", + "description": [ + { + "language": "en", + "text": "Modeling of logical connections between components and sub-components. Either this or \"IsPartOf\" must be used, not both." + } + ], + "semanticId": { + "type": "ExternalReference", + "keys": [ + { + "type": "GlobalReference", + "value": "https://admin-shell.io/idta/HierarchicalStructures/HasPart/1/0" + } + ] + }, + "qualifiers": [ + { + "semanticId": { + "type": "ExternalReference", + "keys": [ + { + "type": "GlobalReference", + "value": "https://admin-shell.io/SubmodelTemplates/Cardinality/1/0" + } + ] + }, + "kind": "TemplateQualifier", + "type": "SMT/Cardinality", + "valueType": "xs:string", + "value": "ZeroToMany" + }, + { + "kind": "TemplateQualifier", + "type": "EditIdShort", + "valueType": "xs:string", + "value": "True" + } + ], + "first": { + "type": "ModelReference", + "keys": [] + }, + "second": { + "type": "ModelReference", + "keys": [] + }, + "modelType": "RelationshipElement" + }, + { + "idShort": "BulkCount", + "description": [ + { + "language": "en", + "text": "To be used if bulk components are referenced, e.g., a 10x M4x30 screw." + } + ], + "semanticId": { + "type": "ExternalReference", + "keys": [ + { + "type": "GlobalReference", + "value": "https://admin-shell.io/idta/HierarchicalStructures/BulkCount/1/0" + } + ] + }, + "qualifiers": [ + { + "semanticId": { + "type": "ExternalReference", + "keys": [ + { + "type": "GlobalReference", + "value": "https://admin-shell.io/SubmodelTemplates/Cardinality/1/0" + } + ] + }, + "kind": "TemplateQualifier", + "type": "SMT/Cardinality", + "valueType": "xs:string", + "value": "ZeroToOne" + } + ], + "valueType": "xs:unsignedLong", + "value": "52467", + "modelType": "Property" + } + ], + "entityType": "SelfManagedEntity", + "globalAssetId": "https://mm-software.com/ids/assets/000-002", + "modelType": "Entity" + }, + { + "idShort": "SameAs", + "description": [ + { + "language": "en", + "text": "Reference between two Entities in the same Submodel or across Submodels." + } + ], + "semanticId": { + "type": "ExternalReference", + "keys": [ + { + "type": "GlobalReference", + "value": "https://admin-shell.io/idta/HierarchicalStructures/SameAs/1/0" + } + ] + }, + "qualifiers": [ + { + "semanticId": { + "type": "ExternalReference", + "keys": [ + { + "type": "GlobalReference", + "value": "https://admin-shell.io/SubmodelTemplates/Cardinality/1/0" + } + ] + }, + "kind": "TemplateQualifier", + "type": "SMT/Cardinality", + "valueType": "xs:string", + "value": "ZeroToMany" + }, + { + "kind": "TemplateQualifier", + "type": "SMT/Cardinality", + "valueType": "xs:string", + "value": "True" + } + ], + "first": { + "type": "ExternalReference", + "keys": [ + { + "type": "GlobalReference", + "value": "https://company.com" + } + ] + }, + "second": { + "type": "ExternalReference", + "keys": [ + { + "type": "GlobalReference", + "value": "https://www.mm-software.com/" + } + ] + }, + "modelType": "RelationshipElement" + }, + { + "idShort": "IsPartOf", + "description": [ + { + "language": "en", + "text": "Modeling of logical connections between asset and sub-asset. Either this or \"HasPart\" must be used, not both." + } + ], + "semanticId": { + "type": "ExternalReference", + "keys": [ + { + "type": "GlobalReference", + "value": "https://admin-shell.io/idta/HierarchicalStructures/IsPartOf/1/0" + } + ] + }, + "qualifiers": [ + { + "semanticId": { + "type": "ExternalReference", + "keys": [ + { + "type": "GlobalReference", + "value": "https://admin-shell.io/SubmodelTemplates/Cardinality/1/0" + } + ] + }, + "kind": "TemplateQualifier", + "type": "SMT/Cardinality", + "valueType": "xs:string", + "value": "ZeroToMany" + }, + { + "kind": "TemplateQualifier", + "type": "SMT/Cardinality", + "valueType": "xs:string", + "value": "True" + } + ], + "first": { + "type": "ExternalReference", + "keys": [ + { + "type": "GlobalReference", + "value": "https://www.mm-software.com/" + } + ] + }, + "second": { + "type": "ModelReference", + "keys": [ + { + "type": "Submodel", + "value": "https://mm-software.com/submodel/000-003/CustomSubmodel" + }, + { + "type": "Property", + "value": "ArcheType" + } + ] + }, + "modelType": "RelationshipElement" + }, + { + "idShort": "HasPart", + "description": [ + { + "language": "en", + "text": "Modeling of logical connections between components and sub-components. Either this or \"IsPartOf\" must be used, not both." + } + ], + "semanticId": { + "type": "ExternalReference", + "keys": [ + { + "type": "GlobalReference", + "value": "https://admin-shell.io/idta/HierarchicalStructures/HasPart/1/0" + } + ] + }, + "qualifiers": [ + { + "semanticId": { + "type": "ExternalReference", + "keys": [ + { + "type": "GlobalReference", + "value": "https://admin-shell.io/SubmodelTemplates/Cardinality/1/0" + } + ] + }, + "kind": "TemplateQualifier", + "type": "SMT/Cardinality", + "valueType": "xs:string", + "value": "ZeroToMany" + }, + { + "kind": "TemplateQualifier", + "type": "SMT/Cardinality", + "valueType": "xs:string", + "value": "True" + } + ], + "first": { + "type": "ModelReference", + "keys": [ + { + "type": "Submodel", + "value": "https://mm-software.com/submodel/000-003/CustomSubmodel" + }, + { + "type": "SubmodelElementList", + "value": "Markings" + }, + { + "type": "SubmodelElementCollection", + "value": "0" + }, + { + "type": "Property", + "value": "DesignationOfCertificateOrApproval" + } + ] + }, + "second": { + "type": "ModelReference", + "keys": [ + { + "type": "Submodel", + "value": "https://mm-software.com/submodel/000-003/ContactInformation" + }, + { + "type": "SubmodelElementCollection", + "value": "ContactInformation0" + }, + { + "type": "Property", + "value": "NameOfContact" + } + ] + }, + "modelType": "RelationshipElement" + } + ], + "entityType": "SelfManagedEntity", + "globalAssetId": "https://mm-software.com/ids/assets/000-003", + "modelType": "Entity" + }, + { + "idShort": "Target_model", + "semanticId": { + "type": "ExternalReference", + "keys": [ + { + "type": "GlobalReference", + "value": "http://admin-shell.io/aasx-package-explorer/functions/asciidoc/target-model/1/0" + } + ] + }, + "qualifiers": [ + { + "semanticId": { + "type": "ExternalReference", + "keys": [ + { + "type": "GlobalReference", + "value": "https://admin-shell.io/SubmodelTemplates/Cardinality/1/0" + } + ] + }, + "kind": "ConceptQualifier", + "type": "SMT/Cardinality", + "valueType": "xs:string", + "value": "One" + } + ], + "value": { + "type": "ModelReference", + "keys": [ + { + "type": "Submodel", + "value": "https://mm-software.com/submodel/000-002/ContactInformation" + } + ] + }, + "modelType": "ReferenceElement" + }, + { + "idShort": "Header", + "semanticId": { + "type": "ExternalReference", + "keys": [ + { + "type": "GlobalReference", + "value": "http://admin-shell.io/aasx-package-explorer/functions/asciidoc/heading1/1/0" + } + ] + }, + "qualifiers": [ + { + "semanticId": { + "type": "ExternalReference", + "keys": [ + { + "type": "GlobalReference", + "value": "https://admin-shell.io/SubmodelTemplates/Cardinality/1/0" + } + ] + }, + "kind": "ConceptQualifier", + "type": "SMT/Cardinality", + "valueType": "xs:string", + "value": "One" + } + ], + "value": "SW5mb3JtYXRpb24gc2V0IGZvciBTdWJtb2RlbCDigJxOYW1lcGxhdGXigJ0=", + "contentType": "text/markdown", + "modelType": "Blob" + }, + { + "category": "PARAMETER", + "idShort": "ArbitraryRange", + "description": [ + { + "language": "en", + "text": "Note: Every range property can be used." + }, + { + "language": "en", + "text": "Note: The idShort is arbitrary." + }, + { + "language": "en", + "text": "Note: The use of a displayName is recommended." + } + ], + "semanticId": { + "type": "ExternalReference", + "keys": [ + { + "type": "GlobalReference", + "value": "https://admin-shell.io/SMT/General/Arbitrary" + } + ] + }, + "qualifiers": [ + { + "semanticId": { + "type": "ExternalReference", + "keys": [ + { + "type": "GlobalReference", + "value": "https://admin-shell.io/SubmodelTemplates/SMT/Cardinality/1/0" + } + ] + }, + "kind": "ConceptQualifier", + "type": "SMT/Cardinality", + "valueType": "xs:string", + "value": "ZeroToMany" + } + ], + "valueType": "xs:double", + "min": "24", + "max": "962", + "modelType": "Range" + }, + { + "category": "CONSTANT", + "idShort": "ArcheType", + "description": [ + { + "language": "en", + "text": "ArcheType of the Submodel, there are three allowed enumeration entries: 1. “Full”, 2. “OneDown” and 3. “OneUp”. " + } + ], + "semanticId": { + "type": "ExternalReference", + "keys": [ + { + "type": "GlobalReference", + "value": "https://admin-shell.io/idta/HierarchicalStructures/ArcheType/1/0" + } + ] + }, + "qualifiers": [ + { + "semanticId": { + "type": "ExternalReference", + "keys": [ + { + "type": "GlobalReference", + "value": "https://admin-shell.io/SubmodelTemplates/Cardinality/1/0" + } + ] + }, + "kind": "TemplateQualifier", + "type": "SMT/Cardinality", + "valueType": "xs:string", + "value": "One" + }, + { + "kind": "TemplateQualifier", + "type": "FormChoices", + "valueType": "xs:string", + "value": "Full;OneDown;OneUp" + } + ], + "valueType": "xs:string", + "value": "000-001 ArcheType", + "modelType": "Property" + }, + { + "idShort": "Markings", + "description": [ + { + "language": "en", + "text": "Note: CE marking is declared as mandatory according to EU Blue Guide" + } + ], + "semanticId": { + "type": "ExternalReference", + "keys": [ + { + "type": "GlobalReference", + "value": "0112/2///61360_7#AAS006#001" + } + ] + }, + "supplementalSemanticIds": [ + { + "type": "ExternalReference", + "keys": [ + { + "type": "GlobalReference", + "value": "0173-1#02-ABI563#003/0173-1#01-AHF849#003" + } + ] + } + ], + "qualifiers": [ + { + "semanticId": { + "type": "ExternalReference", + "keys": [ + { + "type": "GlobalReference", + "value": "https://admin-shell.io/SubmodelTemplates/Cardinality/1/0" + } + ] + }, + "kind": "TemplateQualifier", + "type": "SMT/Cardinality", + "valueType": "xs:string", + "value": "ZeroToOne" + } + ], + "orderRelevant": false, + "typeValueListElement": "SubmodelElementCollection", + "value": [ + { + "idShort": "Marking", + "description": [ + { + "language": "en", + "text": "Note: CE marking is declared as mandatory according to the Blue Guide of the EU-Commission" + } + ], + "semanticId": { + "type": "ExternalReference", + "keys": [ + { + "type": "GlobalReference", + "value": "0112/2///61360_7#AAS009#001#0" + } + ] + }, + "supplementalSemanticIds": [ + { + "type": "ExternalReference", + "keys": [ + { + "type": "GlobalReference", + "value": "0173-1#02-ABI564#003/0173-1#01-AHF850#003" + } + ] + } + ], + "qualifiers": [ + { + "semanticId": { + "type": "ExternalReference", + "keys": [ + { + "type": "GlobalReference", + "value": "https://admin-shell.io/SubmodelTemplates/Cardinality/1/0" + } + ] + }, + "kind": "TemplateQualifier", + "type": "SMT/Cardinality", + "valueType": "xs:string", + "value": "OneToMany" + } + ], + "value": [ + { + "category": "PARAMETER", + "idShort": "ReferenceElementWithOutValue", + "semanticId": { + "type": "ExternalReference", + "keys": [ + { + "type": "GlobalReference", + "value": "https://admin-shell.io/vdi/2770/1/0/Document/DocumentedEntity" + } + ] + }, + "qualifiers": [ + { + "semanticId": { + "type": "ExternalReference", + "keys": [ + { + "type": "GlobalReference", + "value": "https://admin-shell.io/SubmodelTemplates/Cardinality/1/0" + } + ] + }, + "kind": "ConceptQualifier", + "type": "SMT/Cardinality", + "valueType": "xs:string", + "value": "OneToMany" + } + ], + "modelType": "ReferenceElement" + }, + { + "idShort": "MarkingName", + "semanticId": { + "type": "ExternalReference", + "keys": [ + { + "type": "GlobalReference", + "value": "0112/2///61987#ABA231#009" + } + ] + }, + "supplementalSemanticIds": [ + { + "type": "ExternalReference", + "keys": [ + { + "type": "GlobalReference", + "value": "0173-1#02-ABI190#003" + } + ] + } + ], + "qualifiers": [ + { + "semanticId": { + "type": "ExternalReference", + "keys": [ + { + "type": "GlobalReference", + "value": "https://admin-shell.io/SubmodelTemplates/Cardinality/1/0" + } + ] + }, + "kind": "TemplateQualifier", + "type": "SMT/Cardinality", + "valueType": "xs:string", + "value": "One" + } + ], + "valueType": "xs:string", + "value": "0173-1#07-DAA603#004", + "modelType": "Property" + }, + { + "idShort": "DesignationOfCertificateOrApproval", + "description": [ + { + "language": "en", + "text": "Note: Approval identifier, reference to the certificate number, to be entered without spaces " + } + ], + "semanticId": { + "type": "ExternalReference", + "keys": [ + { + "type": "GlobalReference", + "value": "0112/2///61987#ABH783#003" + } + ] + }, + "supplementalSemanticIds": [ + { + "type": "ExternalReference", + "keys": [ + { + "type": "GlobalReference", + "value": "0173-1#02-ABI975#002" + } + ] + } + ], + "qualifiers": [ + { + "semanticId": { + "type": "ExternalReference", + "keys": [ + { + "type": "GlobalReference", + "value": "https://admin-shell.io/SubmodelTemplates/Cardinality/1/0" + } + ] + }, + "kind": "TemplateQualifier", + "type": "SMT/Cardinality", + "valueType": "xs:string", + "value": "ZeroToOne" + } + ], + "valueType": "xs:string", + "value": "KEMA99IECEX1105/128", + "modelType": "Property" + }, + { + "idShort": "IssueDate", + "description": [ + { + "language": "en", + "text": "Note: format by lexical representation: CCYY-MM-DD Note: to be specified to the day " + } + ], + "semanticId": { + "type": "ExternalReference", + "keys": [ + { + "type": "GlobalReference", + "value": "0112/2///61987#ABO097#001" + } + ] + }, + "supplementalSemanticIds": [ + { + "type": "ExternalReference", + "keys": [ + { + "type": "GlobalReference", + "value": "0173-1#02-ABL774#001" + } + ] + } + ], + "qualifiers": [ + { + "semanticId": { + "type": "ExternalReference", + "keys": [ + { + "type": "GlobalReference", + "value": "https://admin-shell.io/SubmodelTemplates/Cardinality/1/0" + } + ] + }, + "kind": "TemplateQualifier", + "type": "SMT/Cardinality", + "valueType": "xs:string", + "value": "ZeroToOne" + } + ], + "valueType": "xs:date", + "value": "2022-01-01", + "modelType": "Property" + }, + { + "idShort": "ExpiryDate", + "description": [ + { + "language": "en", + "text": "Note: format by lexical representation: CCYY-MM-DD Note: to be specified to the day " + } + ], + "semanticId": { + "type": "ExternalReference", + "keys": [ + { + "type": "GlobalReference", + "value": "0112/2///61987#ABH830#002" + } + ] + }, + "supplementalSemanticIds": [ + { + "type": "ExternalReference", + "keys": [ + { + "type": "GlobalReference", + "value": "0173-1#02-ABL775#001" + } + ] + } + ], + "qualifiers": [ + { + "semanticId": { + "type": "ExternalReference", + "keys": [ + { + "type": "GlobalReference", + "value": "https://admin-shell.io/SubmodelTemplates/Cardinality/1/0" + } + ] + }, + "kind": "TemplateQualifier", + "type": "SMT/Cardinality", + "valueType": "xs:string", + "value": "ZeroToOne" + } + ], + "valueType": "xs:date", + "value": "2022-01-01", + "modelType": "Property" + }, + { + "idShort": "MarkingAdditionalText", + "semanticId": { + "type": "ExternalReference", + "keys": [ + { + "type": "GlobalReference", + "value": "0112/2///61987#ABB146#007" + } + ] + }, + "supplementalSemanticIds": [ + { + "type": "ExternalReference", + "keys": [ + { + "type": "GlobalReference", + "value": "0173-1#02-ABI192#003" + } + ] + } + ], + "qualifiers": [ + { + "semanticId": { + "type": "ExternalReference", + "keys": [ + { + "type": "GlobalReference", + "value": "https://admin-shell.io/SubmodelTemplates/Cardinality/1/0" + } + ] + }, + "kind": "TemplateQualifier", + "type": "SMT/Cardinality", + "valueType": "xs:string", + "value": "ZeroToMany" + } + ], + "valueType": "xs:string", + "value": "0044", + "modelType": "Property" + }, + { + "category": "PARAMETER", + "idShort": "ClassName", + "displayName": [ + { + "language": "en", + "text": "Class Name" + }, + { + "language": "en", + "text": "Klassenname" + } + ], + "semanticId": { + "type": "ExternalReference", + "keys": [ + { + "type": "GlobalReference", + "value": "0173-1#02-ABJ219#002" + } + ] + }, + "supplementalSemanticIds": [ + { + "type": "ExternalReference", + "keys": [ + { + "type": "GlobalReference", + "value": "https://api.eclass-cdp.com/0173-1-02-ABJ219-002" + } + ] + } + ], + "qualifiers": [ + { + "semanticId": { + "type": "ExternalReference", + "keys": [ + { + "type": "GlobalReference", + "value": "https://admin-shell.io/SubmodelTemplates/Cardinality/1/0" + } + ] + }, + "kind": "ConceptQualifier", + "type": "SMT/Cardinality", + "valueType": "xs:string", + "value": "One" + }, + { + "semanticId": { + "type": "ExternalReference", + "keys": [ + { + "type": "GlobalReference", + "value": "https://admin-shell.io/SubmodelTemplates/ExampleValue/1/0" + } + ] + }, + "kind": "ConceptQualifier", + "type": "ExampleValue", + "valueType": "xs:string", + "value": "Operation@en" + } + ], + "modelType": "MultiLanguageProperty" + }, + { + "category": "PARAMETER", + "idShort": "ArbitraryRangeString", + "description": [ + { + "language": "en", + "text": "Note: Every range property can be used." + }, + { + "language": "en", + "text": "Note: The idShort is arbitrary." + }, + { + "language": "en", + "text": "Note: The use of a displayName is recommended." + } + ], + "semanticId": { + "type": "ExternalReference", + "keys": [ + { + "type": "GlobalReference", + "value": "https://admin-shell.io/SMT/General/Arbitrary/string" + } + ] + }, + "qualifiers": [ + { + "semanticId": { + "type": "ExternalReference", + "keys": [ + { + "type": "GlobalReference", + "value": "https://admin-shell.io/SubmodelTemplates/SMT/Cardinality/1/0" + } + ] + }, + "kind": "ConceptQualifier", + "type": "SMT/Cardinality", + "valueType": "xs:string", + "value": "ZeroToMany" + } + ], + "valueType": "xs:string", + "min": "2", + "max": "10", + "modelType": "Range" + }, + { + "idShort": "MarkingFile", + "semanticId": { + "type": "ExternalReference", + "keys": [ + { + "type": "GlobalReference", + "value": "0112/2///61987#ABO100#002" + } + ] + }, + "supplementalSemanticIds": [ + { + "type": "ExternalReference", + "keys": [ + { + "type": "GlobalReference", + "value": "0173-1#02-ABI191#003" + } + ] + } + ], + "qualifiers": [ + { + "semanticId": { + "type": "ExternalReference", + "keys": [ + { + "type": "GlobalReference", + "value": "https://admin-shell.io/SubmodelTemplates/Cardinality/1/0" + } + ] + }, + "kind": "TemplateQualifier", + "type": "SMT/Cardinality", + "valueType": "xs:string", + "value": "One" + } + ], + "value": "https://raw.githubusercontent.com/AAS-TwinEngine/AAS.TwinEngine.DataEngine/refs/heads/main/example/data/checkmark.png", + "contentType": "image/png", + "modelType": "File" + } + ], + "modelType": "SubmodelElementCollection" + } + ], + "modelType": "SubmodelElementList" + }, + { + "idShort": "OperatingConditionsOfReliabilityCharacteristics", + "description": [ + { + "language": "en", + "text": "operating conditions of reliability characteristics" + }, + { + "language": "fr", + "text": "conditions de fonctionnement des caractéristiques de fiabilité et de sécurité fonctionnelle" + } + ], + "semanticId": { + "type": "ExternalReference", + "keys": [ + { + "type": "GlobalReference", + "value": "0112/2///62683#ACG071#001" + } + ] + }, + "qualifiers": [ + { + "kind": "ConceptQualifier", + "type": "SMT/Cardinality", + "valueType": "xs:string", + "value": "ZeroToMany" + } + ], + "value": [ + { + "idShort": "CompanyLogo", + "semanticId": { + "type": "ExternalReference", + "keys": [ + { + "type": "GlobalReference", + "value": "https://admin-shell.io/zvei/nameplate/2/0/Nameplate/CompanyLogo" + } + ] + }, + "qualifiers": [ + { + "kind": "ConceptQualifier", + "type": "Multiplicity", + "valueType": "xs:string", + "value": "ZeroToOne" + } + ], + "value": "https://raw.githubusercontent.com/AAS-TwinEngine/AAS.TwinEngine.DataEngine/refs/heads/main/example/logo/MM_Logo.svg", + "contentType": "EMPTY", + "modelType": "File" + }, + { + "category": "PARAMETER", + "idShort": "ArbitraryRangeDate", + "description": [ + { + "language": "en", + "text": "Note: Every range property can be used." + }, + { + "language": "en", + "text": "Note: The idShort is arbitrary." + }, + { + "language": "en", + "text": "Note: The use of a displayName is recommended." + } + ], + "semanticId": { + "type": "ExternalReference", + "keys": [ + { + "type": "GlobalReference", + "value": "https://admin-shell.io/SMT/General/Arbitrary/date" + } + ] + }, + "qualifiers": [ + { + "semanticId": { + "type": "ExternalReference", + "keys": [ + { + "type": "GlobalReference", + "value": "https://admin-shell.io/SubmodelTemplates/SMT/Cardinality/1/0" + } + ] + }, + "kind": "ConceptQualifier", + "type": "SMT/Cardinality", + "valueType": "xs:string", + "value": "ZeroToMany" + } + ], + "valueType": "xs:date", + "min": "2010-02-02", + "max": "2030-02-06", + "modelType": "Range" + }, + { + "idShort": "RatedVoltage", + "description": [ + { + "language": "en", + "text": "rated voltage" + }, + { + "language": "fr", + "text": "tension assignée" + }, + { + "language": "de", + "text": "Bemessungsspannung" + } + ], + "semanticId": { + "type": "ExternalReference", + "keys": [ + { + "type": "GlobalReference", + "value": "0112/2///61987#ABA588#004" + } + ] + }, + "qualifiers": [ + { + "kind": "ConceptQualifier", + "type": "SMT/Cardinality", + "valueType": "xs:string", + "value": "ZeroToMany" + } + ], + "valueType": "xs:double", + "value": "56.5", + "modelType": "Property" + }, + { + "idShort": "MinimumRatedVoltage", + "description": [ + { + "language": "en", + "text": "minimum rated voltage" + }, + { + "language": "fr", + "text": "tension assignée minimale" + }, + { + "language": "de", + "text": "minimale Bemessungsspannung" + } + ], + "semanticId": { + "type": "ExternalReference", + "keys": [ + { + "type": "GlobalReference", + "value": "0112/2///61987#ABD461#004" + } + ] + }, + "qualifiers": [ + { + "kind": "ConceptQualifier", + "type": "SMT/Cardinality", + "valueType": "xs:string", + "value": "ZeroToMany" + } + ], + "valueType": "xs:double", + "value": "12.5", + "modelType": "Property" + }, + { + "idShort": "Paragraph", + "semanticId": { + "type": "ExternalReference", + "keys": [ + { + "type": "GlobalReference", + "value": "http://admin-shell.io/aasx-package-explorer/functions/asciidoc/textblock/1/0" + } + ] + }, + "qualifiers": [ + { + "semanticId": { + "type": "ExternalReference", + "keys": [ + { + "type": "GlobalReference", + "value": "https://admin-shell.io/SubmodelTemplates/Cardinality/1/0" + } + ] + }, + "kind": "ConceptQualifier", + "type": "SMT/Cardinality", + "valueType": "xs:string", + "value": "One" + } + ], + "value": "UmVnYXJkaW5nIHByb3BlcnR5IGBNYXJraW5nTmFtZWAsIHRoZSBwcmVmZXJhYmxlIHNvbHV0aW9uIGlzIHRvIHByb3ZpZGUgYSB2YWx1ZUlkIGluIElSREkgb3JpZ2luYXRpbmcgZnJvbSBJRUMgQ0REIG9yIEVDTEFTUyBlbnVtZXJhdGlvbiB2YWx1ZSBsaXN0LCBlLmcuICJDRSIgKElSREk6IDAxMTIvMi8vLzYxOTg3I0FCTzQwOSMwMDMgb3IgMDE3My0xIzA3LURBQTYwMyMwMDQpLiBJbiBjYXNlIG5vbmUgb2YgdGhlIGV4aXN0aW5nIEVDTEFTUyBlbnVtZXJhdGlvbiB2YWx1ZXMgbWF0Y2hlcywgZmlsbGluZyBwbGFpbiBzdHJpbmcgdGV4dCBpbnRvIHRoZSDigJx2YWx1ZeKAnSBmaWVsZCBvZiB0aGUgcHJvcGVydHkgYE1hcmtpbmdOYW1lYCBjYW4gYmUgYWNjZXB0ZWQgYWx0ZXJuYXRpdmVseS4gSXQgbmVlZHMgdG8gYmUgcG9pbnRlZCBvdXQgdGhhdCBFQ0xBU1MgYWxzbyBwcm92aWRlcyBtYXJraW5nIGRlZmluaXRpb25zIGluIHRlcm1zIG9mIGJvb2xlYW4gcHJvcGVydHksIGUuZy4g4oCcQ0UtIHF1YWxpZmljYXRpb24gcHJlc2VudOKAnSAoSVJESTogMDE3My0xIzAyLUJBRjA1MyMwMDgpLiBJbiB0aGlzIGNhc2UgdXNlcnMgc2hvdWxkIGluc3RlYWQgdXNlIGEgbWF0Y2hpbmcgRUNMQVNTIGVudW1lcmF0aW9uIHZhbHVlIG9yLCBpZiBub3QgcHJvdmlkZWQgYXMgZW51bWVyYXRpb24sIGZpbGwgaW4gcGxhaW4gc3RyaW5nIHRleHQuDQoNClRoZSBmb2xsb3dpbmcgZXhhbXBsZSAoc2VlIDw8RmlndXJlXzU+PikgaWxsdXN0cmF0ZXMgaG93IHRvIG1vZGVsIHByb2R1Y3QgbWFya2luZyBpbiBhbiBBQVMuIE9uIHRoZSBsZWZ0IHNpZGUgdGhlcmUgaXMgYSBzYW1wbGUgbmFtZXBsYXRlIHdoaWNoIGNvbnRhaW5zIHR3byBtYXJraW5ncyB0byBiZSBtb2RlbGxlZDogdGhlIENFIG1hcmtpbmcgYW5kIHRoZSBXRUVFIG1hcmtpbmcgd2l0aCBhIGNyb3NzZWQtb3V0IHdoZWVsZWQgYmluLiBOZXh0IHRvIHRoZSBuYW1lcGxhdGUgYSB0YWJsZSBsaXN0cyBhbGwgcHJvcGVydGllcyBhbmQgdGhlaXIgYXR0cmlidXRlcy4gDQo=", + "contentType": "text/markdown", + "modelType": "Blob" + }, + { + "idShort": "ProductClassName", + "displayName": [ + { + "language": "en", + "text": "Product class name" + }, + { + "language": "de", + "text": "Produktklasse Name" + } + ], + "semanticId": { + "type": "ExternalReference", + "keys": [ + { + "type": "GlobalReference", + "value": "0173-1#02-ABK273#002" + } + ] + }, + "supplementalSemanticIds": [ + { + "type": "ExternalReference", + "keys": [ + { + "type": "GlobalReference", + "value": "https://api.eclass-cdp.com/0173-1-02-ABK273-002" + } + ] + } + ], + "qualifiers": [ + { + "semanticId": { + "type": "ExternalReference", + "keys": [ + { + "type": "GlobalReference", + "value": "https://admin-shell.io/SubmodelTemplates/SMT/Cardinality/1/0" + } + ] + }, + "kind": "ConceptQualifier", + "type": "SMT/Cardinality", + "valueType": "xs:string", + "value": "ZeroToOne" + } + ], + "value": [ + { + "language": "en", + "text": "Name of product class in the used classification system" + }, + { + "language": "de", + "text": "Name der Produktklasse im verwendeten Klassifizierungssystem" + }, + { + "language": "", + "text": "" + } + ], + "modelType": "MultiLanguageProperty" + }, + { + "idShort": "ModelReference", + "displayName": [ + { + "language": "en", + "text": "Reference to technical property area" + }, + { + "language": "de", + "text": "Referenz auf einen technsichen Merkmalsbereich" + } + ], + "semanticId": { + "type": "ExternalReference", + "keys": [ + { + "type": "GlobalReference", + "value": "0173-1#02-ABL358#002" + } + ] + }, + "supplementalSemanticIds": [ + { + "type": "ExternalReference", + "keys": [ + { + "type": "GlobalReference", + "value": "https://api.eclass-cdp.com/0173-1-02-ABL358-002" + } + ] + } + ], + "qualifiers": [ + { + "semanticId": { + "type": "ExternalReference", + "keys": [ + { + "type": "GlobalReference", + "value": "https://admin-shell.io/SubmodelTemplates/SMT/Cardinality/1/0" + } + ] + }, + "kind": "ConceptQualifier", + "type": "SMT/Cardinality", + "valueType": "xs:string", + "value": "ZeroToOne" + } + ], + "value": { + "type": "ModelReference", + "keys": [ + { + "type": "Submodel", + "value": "https://mm-software.com/submodel/000-003/HandoverDocumentation" + }, + { + "type": "SubmodelElementList", + "value": "Documents" + }, + { + "type": "SubmodelElementCollection", + "value": "0" + } + ] + }, + "modelType": "ReferenceElement" + } + ], + "modelType": "SubmodelElementCollection" + } + ], + "modelType": "Submodel" +} diff --git a/source/AAS.TwinEngine.Plugin.TestPlugin/Example/apiCollection/Aas Repository/Get Submodel by submodelId and aaslId.bru b/source/AAS.TwinEngine.Plugin.TestPlugin/Example/apiCollection/Aas Repository/Get Submodel by submodelId and aaslId.bru index 9b31d0b3..f1da7644 100644 --- a/source/AAS.TwinEngine.Plugin.TestPlugin/Example/apiCollection/Aas Repository/Get Submodel by submodelId and aaslId.bru +++ b/source/AAS.TwinEngine.Plugin.TestPlugin/Example/apiCollection/Aas Repository/Get Submodel by submodelId and aaslId.bru @@ -16,7 +16,7 @@ params:query { } params:path { - submodelIdentifier: {{submodelIdentifierHandoverDocumentation}} + submodelIdentifier: {{submodelIdentifierCustomSubmodel}} aasIdentifier: {{aasIdentifier}} } diff --git a/source/AAS.TwinEngine.Plugin.TestPlugin/Example/apiCollection/Aas Repository/Get Submodel-element by idshort and aasID.bru b/source/AAS.TwinEngine.Plugin.TestPlugin/Example/apiCollection/Aas Repository/Get Submodel-element by idshort and aasID.bru index 911eea74..3b959f9c 100644 --- a/source/AAS.TwinEngine.Plugin.TestPlugin/Example/apiCollection/Aas Repository/Get Submodel-element by idshort and aasID.bru +++ b/source/AAS.TwinEngine.Plugin.TestPlugin/Example/apiCollection/Aas Repository/Get Submodel-element by idshort and aasID.bru @@ -10,6 +10,11 @@ get { auth: inherit } +params:query { + ~level: + ~extent: +} + params:path { idShortPath: Documents[0].DocumentClassifications[0].ClassName aasIdentifier: {{aasIdentifier}} diff --git a/source/AAS.TwinEngine.Plugin.TestPlugin/Example/apiCollection/Aas Repository/Get Submodel-elements by aasID.bru b/source/AAS.TwinEngine.Plugin.TestPlugin/Example/apiCollection/Aas Repository/Get Submodel-elements by aasID.bru index e39a639c..7310ba8c 100644 --- a/source/AAS.TwinEngine.Plugin.TestPlugin/Example/apiCollection/Aas Repository/Get Submodel-elements by aasID.bru +++ b/source/AAS.TwinEngine.Plugin.TestPlugin/Example/apiCollection/Aas Repository/Get Submodel-elements by aasID.bru @@ -10,6 +10,11 @@ get { auth: inherit } +params:query { + ~limit: + ~cursor: +} + params:path { aasIdentifier: {{aasIdentifier}} submodelIdentifier: {{submodelIdentifierHandoverDocumentation}} From 54b7fea8d43c7a3727c91dcea9ca567c1a669c6e Mon Sep 17 00:00:00 2001 From: Hardi Shah Date: Tue, 4 Aug 2026 17:11:59 +0530 Subject: [PATCH 06/25] Remove this unnecessary check for null. --- .../Api/AasRepository/Handler/AasRepositoryHandler.cs | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/source/AAS.TwinEngine.DataEngine/Api/AasRepository/Handler/AasRepositoryHandler.cs b/source/AAS.TwinEngine.DataEngine/Api/AasRepository/Handler/AasRepositoryHandler.cs index db51d994..995d9528 100644 --- a/source/AAS.TwinEngine.DataEngine/Api/AasRepository/Handler/AasRepositoryHandler.cs +++ b/source/AAS.TwinEngine.DataEngine/Api/AasRepository/Handler/AasRepositoryHandler.cs @@ -91,8 +91,8 @@ public Task GetAllSubmodelElementsByAasIdAsync( var queryOptions = new SubmodelQueryOptions(request.Level.ToString(), request.Extent.ToString()); return GetResourceByIdAsync( - request?.AasIdentifier, - request?.SubmodelId, + request.AasIdentifier, + request.SubmodelId, "submodel elements By AasID", async (aasId, submodelId) => (await aasRepositoryService .GetAllSubmodelElementsByAasIdAsync(aasId, submodelId, queryOptions, request?.Limit, request?.Cursor, cancellationToken) From 4fd7d6a4d534cd3772b8aceffda8b20e102524a2 Mon Sep 17 00:00:00 2001 From: Hardi Shah Date: Tue, 4 Aug 2026 17:26:17 +0530 Subject: [PATCH 07/25] Remove this unnecessary check for null. --- .../Api/AasRepository/Handler/AasRepositoryHandler.cs | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/source/AAS.TwinEngine.DataEngine/Api/AasRepository/Handler/AasRepositoryHandler.cs b/source/AAS.TwinEngine.DataEngine/Api/AasRepository/Handler/AasRepositoryHandler.cs index 995d9528..5e174ec0 100644 --- a/source/AAS.TwinEngine.DataEngine/Api/AasRepository/Handler/AasRepositoryHandler.cs +++ b/source/AAS.TwinEngine.DataEngine/Api/AasRepository/Handler/AasRepositoryHandler.cs @@ -75,8 +75,8 @@ public Task GetSubmodelByAasIdAsync(GetSubmodelByAasRequest request, var queryOptions = new SubmodelQueryOptions(request.Level.ToString(), request.Extent.ToString()); return GetResourceByIdAsync( - request?.AasIdentifier, - request?.SubmodelId, + request.AasIdentifier, + request.SubmodelId, "submodel By AasID", (aasId, submodelId) => aasRepositoryService.GetSubmodelByAasIdAsync(aasId, submodelId, queryOptions, cancellationToken)); } @@ -106,8 +106,8 @@ public Task GetSubmodelElementByAasIdAsync( request?.IdShortPath.ValidateIdShortPath(nameof(request.IdShortPath), logger); return GetResourceByIdAsync( - request?.AasIdentifier, - request?.SubmodelId, + request.AasIdentifier, + request.SubmodelId, "submodel element By AasID", (aasId, submodelId) => aasRepositoryService.GetSubmodelElementByAasIdAsync(aasId, submodelId, request?.IdShortPath, cancellationToken)); } From 72d20edb652ce05d2ec6045c157959256c75071d Mon Sep 17 00:00:00 2001 From: Hardi Shah Date: Fri, 7 Aug 2026 15:42:46 +0530 Subject: [PATCH 08/25] Add file attachment retrieval functionality and update API endpoints - Implemented new API endpoints for retrieving file attachments by idShortPath and aasId. - Updated existing endpoints to include new query parameters for level and extent. - Added unit tests to validate the new functionality and ensure proper error handling. --- ... Submodel-element by idshort and aasID.bru | 7 ++- .../Get Submodel-elements by aasID.bru | 8 +-- ...t file attachment by idshort and aasID.bru | 22 ++++++++ .../Get Submodel-elements by aasID.bru | 8 +-- ...t file attachment by idshort and aasID.bru | 22 ++++++++ .../Get Submodel-elements by aasID.bru | 2 + ...t file attachment by idshort and aasID.bru | 22 ++++++++ .../AasRepositoryControllerTests.cs | 31 ++++++++++++ .../AasRepositoryControllerTests.cs | 21 ++++++++ .../Handler/AasRepositoryHandlerTests.cs | 50 +++++++++++++++++++ .../AasRepository/AasRepositoryController.cs | 30 +++++++++++ .../Handler/AasRepositoryHandler.cs | 22 ++++++-- .../Handler/IAasRepositoryHandler.cs | 3 ++ .../Requests/GetFileByPathByAasIdRequest.cs | 3 ++ .../AasRepository/AasRepositoryService.cs | 7 +++ .../AasRepository/IAasRepositoryService.cs | 3 ++ .../AasRepository/AasRepositoryTests.cs | 13 +++++ ...t file attachment by idshort and aasID.bru | 22 ++++++++ 18 files changed, 286 insertions(+), 10 deletions(-) create mode 100644 example/apiCollection/Aas Repository/Product1/Get file attachment by idshort and aasID.bru create mode 100644 example/apiCollection/Aas Repository/Product2/Get file attachment by idshort and aasID.bru create mode 100644 example/apiCollection/Aas Repository/Product3/Get file attachment by idshort and aasID.bru create mode 100644 source/AAS.TwinEngine.DataEngine/Api/AasRepository/Requests/GetFileByPathByAasIdRequest.cs create mode 100644 source/AAS.TwinEngine.Plugin.TestPlugin/Example/apiCollection/Aas Repository/Get file attachment by idshort and aasID.bru diff --git a/example/apiCollection/Aas Repository/Product1/Get Submodel-element by idshort and aasID.bru b/example/apiCollection/Aas Repository/Product1/Get Submodel-element by idshort and aasID.bru index bb8499b6..a333a484 100644 --- a/example/apiCollection/Aas Repository/Product1/Get Submodel-element by idshort and aasID.bru +++ b/example/apiCollection/Aas Repository/Product1/Get Submodel-element by idshort and aasID.bru @@ -5,11 +5,16 @@ meta { } get { - url: {{DataEngineBaseUrl}}/shells/:aasIdentifier/submodels/:submodelIdentifier/submodel-elements/:idShortPath + url: {{DataEngineBaseUrl}}/shells/:aasIdentifier/submodels/:submodelIdentifier/submodel-elements/:idShortPath?level=&extent= body: none auth: inherit } +params:query { + level: + extent: +} + params:path { idShortPath: ManufacturerName aasIdentifier: {{aasIdentifier-1}} diff --git a/example/apiCollection/Aas Repository/Product1/Get Submodel-elements by aasID.bru b/example/apiCollection/Aas Repository/Product1/Get Submodel-elements by aasID.bru index b11bc588..38af3607 100644 --- a/example/apiCollection/Aas Repository/Product1/Get Submodel-elements by aasID.bru +++ b/example/apiCollection/Aas Repository/Product1/Get Submodel-elements by aasID.bru @@ -5,14 +5,16 @@ meta { } get { - url: {{DataEngineBaseUrl}}/shells/:aasIdentifier/submodels/:submodelIdentifier/submodel-elements?limit=&cursor= + url: {{DataEngineBaseUrl}}/shells/:aasIdentifier/submodels/:submodelIdentifier/submodel-elements body: none auth: inherit } params:query { - limit: - cursor: + ~limit: + ~cursor: + ~level: + ~extent: } params:path { diff --git a/example/apiCollection/Aas Repository/Product1/Get file attachment by idshort and aasID.bru b/example/apiCollection/Aas Repository/Product1/Get file attachment by idshort and aasID.bru new file mode 100644 index 00000000..5969a8de --- /dev/null +++ b/example/apiCollection/Aas Repository/Product1/Get file attachment by idshort and aasID.bru @@ -0,0 +1,22 @@ +meta { + name: Get file attachment by idshort and aasID + type: http + seq: 8 +} + +get { + url: {{DataEngineBaseUrl}}/shells/:aasIdentifier/submodels/:submodelIdentifier/submodel-elements/:idShortPath/attachment + body: none + auth: inherit +} + +params:path { + idShortPath: Documents%255B0%255D.DocumentVersions%255B0%255D.DigitalFiles%255B0%255D + aasIdentifier: {{aasIdentifier-1}} + submodelIdentifier: {{submodelIdentifierHandoverDocumentation-1}} +} + +settings { + encodeUrl: true + timeout: 0 +} diff --git a/example/apiCollection/Aas Repository/Product2/Get Submodel-elements by aasID.bru b/example/apiCollection/Aas Repository/Product2/Get Submodel-elements by aasID.bru index 2c762290..155f570f 100644 --- a/example/apiCollection/Aas Repository/Product2/Get Submodel-elements by aasID.bru +++ b/example/apiCollection/Aas Repository/Product2/Get Submodel-elements by aasID.bru @@ -5,14 +5,16 @@ meta { } get { - url: {{DataEngineBaseUrl}}/shells/:aasIdentifier/submodels/:submodelIdentifier/submodel-elements?cursor=&limit= + url: {{DataEngineBaseUrl}}/shells/:aasIdentifier/submodels/:submodelIdentifier/submodel-elements body: none auth: inherit } params:query { - cursor: - limit: + ~limit: + ~cursor: + ~level: + ~extent: } params:path { diff --git a/example/apiCollection/Aas Repository/Product2/Get file attachment by idshort and aasID.bru b/example/apiCollection/Aas Repository/Product2/Get file attachment by idshort and aasID.bru new file mode 100644 index 00000000..42ca2e9e --- /dev/null +++ b/example/apiCollection/Aas Repository/Product2/Get file attachment by idshort and aasID.bru @@ -0,0 +1,22 @@ +meta { + name: Get file attachment by idshort and aasID + type: http + seq: 9 +} + +get { + url: {{DataEngineBaseUrl}}/shells/:aasIdentifier/submodels/:submodelIdentifier/submodel-elements/:idShortPath/attachment + body: none + auth: inherit +} + +params:path { + idShortPath: Markings[0].MarkingFile + aasIdentifier: {{aasIdentifier-2}} + submodelIdentifier: {{submodelIdentifierNameplate-2}} +} + +settings { + encodeUrl: true + timeout: 0 +} diff --git a/example/apiCollection/Aas Repository/Product3/Get Submodel-elements by aasID.bru b/example/apiCollection/Aas Repository/Product3/Get Submodel-elements by aasID.bru index 5ef175e3..0e3e5ef3 100644 --- a/example/apiCollection/Aas Repository/Product3/Get Submodel-elements by aasID.bru +++ b/example/apiCollection/Aas Repository/Product3/Get Submodel-elements by aasID.bru @@ -13,6 +13,8 @@ get { params:query { ~limit: ~cursor: + ~level: + ~extent: } params:path { diff --git a/example/apiCollection/Aas Repository/Product3/Get file attachment by idshort and aasID.bru b/example/apiCollection/Aas Repository/Product3/Get file attachment by idshort and aasID.bru new file mode 100644 index 00000000..113f0a19 --- /dev/null +++ b/example/apiCollection/Aas Repository/Product3/Get file attachment by idshort and aasID.bru @@ -0,0 +1,22 @@ +meta { + name: Get file attachment by idshort and aasID + type: http + seq: 7 +} + +get { + url: {{DataEngineBaseUrl}}/shells/:aasIdentifier/submodels/:submodelIdentifier/submodel-elements/:idShortPath/attachment + body: none + auth: inherit +} + +params:path { + idShortPath: Markings[0].MarkingFile + aasIdentifier: {{aasIdentifier-3}} + submodelIdentifier: {{submodelIdentifierNameplate-3}} +} + +settings { + encodeUrl: true + timeout: 0 +} diff --git a/source/AAS.TwinEngine.DataEngine.ModuleTests/Api/Services/AasRepository/AasRepositoryControllerTests.cs b/source/AAS.TwinEngine.DataEngine.ModuleTests/Api/Services/AasRepository/AasRepositoryControllerTests.cs index 45d78a51..db7457f6 100644 --- a/source/AAS.TwinEngine.DataEngine.ModuleTests/Api/Services/AasRepository/AasRepositoryControllerTests.cs +++ b/source/AAS.TwinEngine.DataEngine.ModuleTests/Api/Services/AasRepository/AasRepositoryControllerTests.cs @@ -654,6 +654,37 @@ public async Task GetSubmodelElementByAasIdAsync_ReturnsOkAsync() Assert.NotNull(json); } + [Fact] + public async Task GetFileByPathByAasIdAsync_ReturnsAttachmentStreamAsync() + { + // Arrange + const string AasIdentifier = "aHR0cHM6Ly9leGFtcGxlLmNvbS9pZHMvYWFzLzExNzBfMTE2MF8zMDUyXzY1Njg="; + const string submodelKey = "Nameplate"; + const string productId = "1170_1160_3052_6568"; + var requestedSubmodelId = $"https://mm-software.com/submodel/{productId}/{submodelKey}"; + const string idShortPath = "Thumbnail"; + var encodedSubmodelId = EncodeBase64Url(requestedSubmodelId); + var fileBytes = Encoding.UTF8.GetBytes("fake-image-bytes"); + + _ = _mockTemplateProvider.GetSubmodelRefByIdAsync(Arg.Any(), Arg.Any()) + .Returns([new Reference(ReferenceTypes.ModelReference, [new Key(KeyTypes.Submodel, submodelKey)], null)]); + + _ = _mockSubmodelRepositoryService + .GetFileAttachmentAsync(requestedSubmodelId, idShortPath, Arg.Any()) + .Returns(new FileAttachmentResult(new MemoryStream(fileBytes), "image/png", "logo.png", 100 * 1024 * 1024)); + + // Act + var response = await _client.GetAsync($"/shells/{AasIdentifier}/submodels/{encodedSubmodelId}/submodel-elements/{idShortPath}/attachment"); + + // Assert + Assert.Equal(HttpStatusCode.OK, response.StatusCode); + Assert.Equal("image/png", response.Content.Headers.ContentType?.MediaType); + var body = await response.Content.ReadAsByteArrayAsync(); + Assert.Equal(fileBytes, body); + Assert.Contains("logo.png", response.Content.Headers.ContentDisposition?.ToString(), StringComparison.Ordinal); + await _mockSubmodelRepositoryService.Received(1).GetFileAttachmentAsync(requestedSubmodelId, idShortPath, Arg.Any()); + } + private static string EncodeBase64Url(string plainText) { if (string.IsNullOrWhiteSpace(plainText)) diff --git a/source/AAS.TwinEngine.DataEngine.UnitTests/Api/AasRepository/AasRepositoryControllerTests.cs b/source/AAS.TwinEngine.DataEngine.UnitTests/Api/AasRepository/AasRepositoryControllerTests.cs index 1d0f8677..60a4654d 100644 --- a/source/AAS.TwinEngine.DataEngine.UnitTests/Api/AasRepository/AasRepositoryControllerTests.cs +++ b/source/AAS.TwinEngine.DataEngine.UnitTests/Api/AasRepository/AasRepositoryControllerTests.cs @@ -7,9 +7,11 @@ using AAS.TwinEngine.DataEngine.Api.AasRepository.Requests; using AAS.TwinEngine.DataEngine.Api.AasRepository.Responses; using AAS.TwinEngine.DataEngine.Api.Shared; +using AAS.TwinEngine.DataEngine.Api.Shared.Results; using AAS.TwinEngine.DataEngine.Api.SubmodelRepository.Requests; using AAS.TwinEngine.DataEngine.Api.SubmodelRepository.Responses; using AAS.TwinEngine.DataEngine.ApplicationLogic.Extensions; +using AAS.TwinEngine.DataEngine.DomainModel.Shared; using AasCore.Aas3_1; @@ -334,5 +336,24 @@ await _handler.Received(1).GetSubmodelElementByAasIdAsync( Arg.Any()); } + [Fact] + public async Task GetFileByPathByAasIdAsync_ReturnsFileContentStreamResult() + { + var encodedAasId = AasIdentifier.EncodeBase64Url(); + const string SubmodelId = "SubmodelId"; + var encodedSubmodelId = SubmodelId.EncodeBase64Url(); + const string IdShortPath = "Thumbnail"; + + _handler.GetFileByPathByAasIdAsync(Arg.Any(), Arg.Any()) + .Returns(new FileAttachmentResult(Stream.Null, "image/png", "logo.png", 100 * 1024 * 1024)); + + var result = await _sut.GetFileByPathByAasIdAsync(encodedAasId, encodedSubmodelId, IdShortPath, CancellationToken.None); + + Assert.IsType(result); + await _handler.Received(1).GetFileByPathByAasIdAsync( + Arg.Is(r => r.AasIdentifier == encodedAasId && r.SubmodelIdentifier == encodedSubmodelId && r.IdShortPath == IdShortPath), + Arg.Any()); + } + } diff --git a/source/AAS.TwinEngine.DataEngine.UnitTests/Api/AasRepository/Handler/AasRepositoryHandlerTests.cs b/source/AAS.TwinEngine.DataEngine.UnitTests/Api/AasRepository/Handler/AasRepositoryHandlerTests.cs index b2c7d810..ea84b200 100644 --- a/source/AAS.TwinEngine.DataEngine.UnitTests/Api/AasRepository/Handler/AasRepositoryHandlerTests.cs +++ b/source/AAS.TwinEngine.DataEngine.UnitTests/Api/AasRepository/Handler/AasRepositoryHandlerTests.cs @@ -456,5 +456,55 @@ public async Task GetSubmodelElementByAasIdAsync_InvalidIdShortPath_ThrowsInvali await Assert.ThrowsAsync(() => _sut.GetSubmodelElementByAasIdAsync(request, CancellationToken.None)); } + + [Fact] + public async Task GetFileByPathByAasIdAsync_ReturnsAttachment_WhenInputIsValid() + { + const string aasId = "https://example.com/aas/1"; + const string submodelId = "https://example.com/submodels/contact"; + const string idShortPath = "Thumbnail"; + + var request = new GetFileByPathByAasIdRequest( + aasId.EncodeBase64Url(), + submodelId.EncodeBase64Url(), + idShortPath); + + var expected = new FileAttachmentResult(Stream.Null, "image/png", "thumbnail.png", 100 * 1024 * 1024); + + _aasRepositoryService.GetFileAttachmentByAasIdAsync( + aasId, + submodelId, + idShortPath, + Arg.Any()) + .Returns(expected); + + var result = await _sut.GetFileByPathByAasIdAsync(request, CancellationToken.None); + + Assert.Same(expected, result); + await _aasRepositoryService.Received(1).GetFileAttachmentByAasIdAsync( + aasId, + submodelId, + idShortPath, + Arg.Any()); + } + + [Theory] + [InlineData("../../../etc/passwd")] + [InlineData(@"..\\..\\windows\\system32")] + [InlineData("element/../otherElement")] + [InlineData("%2e%2e/config")] + public async Task GetFileByPathByAasIdAsync_InvalidIdShortPath_ThrowsInvalidUserInputException(string maliciousPath) + { + const string aasId = "https://example.com/aas/1"; + const string submodelId = "https://example.com/submodels/contact"; + + var request = new GetFileByPathByAasIdRequest( + aasId.EncodeBase64Url(), + submodelId.EncodeBase64Url(), + maliciousPath); + + await Assert.ThrowsAsync(() => + _sut.GetFileByPathByAasIdAsync(request, CancellationToken.None)); + } } diff --git a/source/AAS.TwinEngine.DataEngine/Api/AasRepository/AasRepositoryController.cs b/source/AAS.TwinEngine.DataEngine/Api/AasRepository/AasRepositoryController.cs index cfd95a7b..50ea1a53 100644 --- a/source/AAS.TwinEngine.DataEngine/Api/AasRepository/AasRepositoryController.cs +++ b/source/AAS.TwinEngine.DataEngine/Api/AasRepository/AasRepositoryController.cs @@ -5,6 +5,8 @@ using AAS.TwinEngine.DataEngine.Api.AasRepository.Handler; using AAS.TwinEngine.DataEngine.Api.AasRepository.Requests; using AAS.TwinEngine.DataEngine.Api.AasRepository.Responses; +using AAS.TwinEngine.DataEngine.Api.Shared.Results; +using AAS.TwinEngine.DataEngine.Api.SubmodelRepository.Handler; using AAS.TwinEngine.DataEngine.Api.SubmodelRepository.Requests; using AAS.TwinEngine.DataEngine.Api.SubmodelRepository.Responses; using AAS.TwinEngine.DataEngine.ApplicationLogic.Exceptions.Responses; @@ -217,4 +219,32 @@ public async Task> GetSubmodelElementByAasIdAsync( var response = await aasRepositoryHandler.GetSubmodelElementByAasIdAsync(request, cancellationToken).ConfigureAwait(false); return Ok(Jsonization.Serialize.ToJsonObject(response)); } + + /// + /// Downloads file content from a specific submodel element from the Submodel at a specified path + /// + /// The Asset Administration Shell's unique id (UTF8-BASE64-URL-encoded) + /// The Submodel's unique id (UTF8-BASE64-URL-encoded) + /// IdShort path to the submodel element (dot-notation) + /// Cancellation token. + /// Requested file + /// Bad Request, e.g. the request parameters of the format of the request body is wrong. + /// Not Found + /// Internal Server Error + [HttpGet("{aasIdentifier}/submodels/{submodelIdentifier}/submodel-elements/{idShortPath}/attachment")] + [ProducesResponseType(typeof(FileResult), StatusCodes.Status200OK)] + [ProducesResponseType(typeof(ServiceErrorResponse), StatusCodes.Status400BadRequest)] + [ProducesResponseType(typeof(ServiceErrorResponse), StatusCodes.Status404NotFound)] + [ProducesResponseType(typeof(ServiceErrorResponse), StatusCodes.Status500InternalServerError)] + public async Task GetFileByPathByAasIdAsync( + [FromRoute] string aasIdentifier, + [FromRoute] string submodelIdentifier, + [FromRoute] string idShortPath, + CancellationToken cancellationToken) + { + logger.LogInformation("Get File through superpath for AAS"); + var request = new GetFileByPathByAasIdRequest(aasIdentifier, submodelIdentifier, idShortPath); + var attachment = await aasRepositoryHandler.GetFileByPathByAasIdAsync(request, cancellationToken).ConfigureAwait(false); + return new FileContentStreamResult(attachment); + } } diff --git a/source/AAS.TwinEngine.DataEngine/Api/AasRepository/Handler/AasRepositoryHandler.cs b/source/AAS.TwinEngine.DataEngine/Api/AasRepository/Handler/AasRepositoryHandler.cs index 5e174ec0..3dbce657 100644 --- a/source/AAS.TwinEngine.DataEngine/Api/AasRepository/Handler/AasRepositoryHandler.cs +++ b/source/AAS.TwinEngine.DataEngine/Api/AasRepository/Handler/AasRepositoryHandler.cs @@ -10,6 +10,7 @@ using AAS.TwinEngine.DataEngine.ApplicationLogic.Extensions; using AAS.TwinEngine.DataEngine.ApplicationLogic.Services.AasRepository; using AAS.TwinEngine.DataEngine.DomainModel.AasRepository; +using AAS.TwinEngine.DataEngine.DomainModel.Shared; using AAS.TwinEngine.DataEngine.DomainModel.SubmodelRepository; using AasCore.Aas3_1; @@ -78,7 +79,7 @@ public Task GetSubmodelByAasIdAsync(GetSubmodelByAasRequest request, request.AasIdentifier, request.SubmodelId, "submodel By AasID", - (aasId, submodelId) => aasRepositoryService.GetSubmodelByAasIdAsync(aasId, submodelId, queryOptions, cancellationToken)); + async (aasId, submodelId) => await aasRepositoryService.GetSubmodelByAasIdAsync(aasId, submodelId, queryOptions, cancellationToken).ConfigureAwait(false)); } public Task GetAllSubmodelElementsByAasIdAsync( @@ -103,13 +104,28 @@ public Task GetSubmodelElementByAasIdAsync( GetSubmodelElementByAasRequest request, CancellationToken cancellationToken) { - request?.IdShortPath.ValidateIdShortPath(nameof(request.IdShortPath), logger); + var decodedIdShortPath = Uri.UnescapeDataString(request?.IdShortPath ?? string.Empty); + decodedIdShortPath.ValidateIdShortPath(nameof(request.IdShortPath), logger); return GetResourceByIdAsync( request.AasIdentifier, request.SubmodelId, "submodel element By AasID", - (aasId, submodelId) => aasRepositoryService.GetSubmodelElementByAasIdAsync(aasId, submodelId, request?.IdShortPath, cancellationToken)); + async (aasId, submodelId) => await aasRepositoryService.GetSubmodelElementByAasIdAsync(aasId, submodelId, request?.IdShortPath, cancellationToken).ConfigureAwait(false)); + } + + public Task GetFileByPathByAasIdAsync( + GetFileByPathByAasIdRequest request, + CancellationToken cancellationToken) + { + var decodedIdShortPath = Uri.UnescapeDataString(request?.IdShortPath ?? string.Empty); + decodedIdShortPath.ValidateIdShortPath(nameof(request.IdShortPath), logger); + + return GetResourceByIdAsync( + request.AasIdentifier, + request.SubmodelIdentifier, + "file By AasID", + async (aasId, submodelId) => await aasRepositoryService.GetFileAttachmentByAasIdAsync(aasId, submodelId, decodedIdShortPath, cancellationToken).ConfigureAwait(false)); } private Task GetResourceByIdAsync( diff --git a/source/AAS.TwinEngine.DataEngine/Api/AasRepository/Handler/IAasRepositoryHandler.cs b/source/AAS.TwinEngine.DataEngine/Api/AasRepository/Handler/IAasRepositoryHandler.cs index 13c841d1..a4bab3c9 100644 --- a/source/AAS.TwinEngine.DataEngine/Api/AasRepository/Handler/IAasRepositoryHandler.cs +++ b/source/AAS.TwinEngine.DataEngine/Api/AasRepository/Handler/IAasRepositoryHandler.cs @@ -3,6 +3,7 @@ using AAS.TwinEngine.DataEngine.Api.AasRepository.Requests; using AAS.TwinEngine.DataEngine.Api.AasRepository.Responses; using AAS.TwinEngine.DataEngine.Api.SubmodelRepository.Responses; +using AAS.TwinEngine.DataEngine.DomainModel.Shared; using AasCore.Aas3_1; @@ -23,4 +24,6 @@ public interface IAasRepositoryHandler Task GetAllSubmodelElementsByAasIdAsync(GetAllSubmodelElementsByAasRequest request, CancellationToken cancellationToken); Task GetSubmodelElementByAasIdAsync(GetSubmodelElementByAasRequest request, CancellationToken cancellationToken); + + Task GetFileByPathByAasIdAsync(GetFileByPathByAasIdRequest request, CancellationToken cancellationToken); } diff --git a/source/AAS.TwinEngine.DataEngine/Api/AasRepository/Requests/GetFileByPathByAasIdRequest.cs b/source/AAS.TwinEngine.DataEngine/Api/AasRepository/Requests/GetFileByPathByAasIdRequest.cs new file mode 100644 index 00000000..6c7489d6 --- /dev/null +++ b/source/AAS.TwinEngine.DataEngine/Api/AasRepository/Requests/GetFileByPathByAasIdRequest.cs @@ -0,0 +1,3 @@ +namespace AAS.TwinEngine.DataEngine.Api.AasRepository.Requests; + +public record GetFileByPathByAasIdRequest(string AasIdentifier, string SubmodelIdentifier, string IdShortPath); diff --git a/source/AAS.TwinEngine.DataEngine/ApplicationLogic/Services/AasRepository/AasRepositoryService.cs b/source/AAS.TwinEngine.DataEngine/ApplicationLogic/Services/AasRepository/AasRepositoryService.cs index 16fb8313..3faf164a 100644 --- a/source/AAS.TwinEngine.DataEngine/ApplicationLogic/Services/AasRepository/AasRepositoryService.cs +++ b/source/AAS.TwinEngine.DataEngine/ApplicationLogic/Services/AasRepository/AasRepositoryService.cs @@ -148,6 +148,13 @@ public async Task GetSubmodelElementByAasIdAsync(string aasIde return await submodelRepositoryService.GetSubmodelElementAsync(submodelIdentifier, idShortPath, cancellationToken).ConfigureAwait(false); } + public async Task GetFileAttachmentByAasIdAsync(string aasIdentifier, string submodelId, string idShortPath, CancellationToken cancellationToken) + { + await ValidateSubmodelBelongsToAasAsync(aasIdentifier, submodelId, cancellationToken).ConfigureAwait(false); + + return await submodelRepositoryService.GetFileAttachmentAsync(submodelId, idShortPath, cancellationToken).ConfigureAwait(false); + } + public async Task ValidateSubmodelBelongsToAasAsync(string aasIdentifier, string submodelIdentifier, CancellationToken cancellationToken) { var submodelRefs = await GetSubmodelRefByIdAsync(aasIdentifier, null, null, cancellationToken).ConfigureAwait(false); diff --git a/source/AAS.TwinEngine.DataEngine/ApplicationLogic/Services/AasRepository/IAasRepositoryService.cs b/source/AAS.TwinEngine.DataEngine/ApplicationLogic/Services/AasRepository/IAasRepositoryService.cs index 5b893f4f..cb8c29de 100644 --- a/source/AAS.TwinEngine.DataEngine/ApplicationLogic/Services/AasRepository/IAasRepositoryService.cs +++ b/source/AAS.TwinEngine.DataEngine/ApplicationLogic/Services/AasRepository/IAasRepositoryService.cs @@ -1,4 +1,5 @@ using AAS.TwinEngine.DataEngine.DomainModel.AasRepository; +using AAS.TwinEngine.DataEngine.DomainModel.Shared; using AAS.TwinEngine.DataEngine.DomainModel.SubmodelRepository; using AasCore.Aas3_1; @@ -22,4 +23,6 @@ public interface IAasRepositoryService Task GetAllSubmodelElementsByAasIdAsync(string aasIdentifier, string submodelIdentifier, SubmodelQueryOptions queryOptions, int? limit, string? cursor, CancellationToken cancellationToken); Task GetSubmodelElementByAasIdAsync(string aasIdentifier, string submodelIdentifier, string idShortPath, CancellationToken cancellationToken); + + Task GetFileAttachmentByAasIdAsync(string aasIdentifier, string submodelId, string idShortPath, CancellationToken cancellationToken); } diff --git a/source/AAS.TwinEngine.Plugin.TestPlugin.PlaywrightTests/AasRepository/AasRepositoryTests.cs b/source/AAS.TwinEngine.Plugin.TestPlugin.PlaywrightTests/AasRepository/AasRepositoryTests.cs index c310fbb7..95fcdd30 100644 --- a/source/AAS.TwinEngine.Plugin.TestPlugin.PlaywrightTests/AasRepository/AasRepositoryTests.cs +++ b/source/AAS.TwinEngine.Plugin.TestPlugin.PlaywrightTests/AasRepository/AasRepositoryTests.cs @@ -413,6 +413,19 @@ public async Task GetAllSubmodelElementsByAasId_WithInvalidLimit_ShouldReturnBad Assert.Equal(400, response.Status); } + [Fact] + public async Task GetFileByPathByAasId_WithUnknownElement_ShouldReturnNotFound() + { + // Arrange + var url = $"/shells/{AasIdentifier}/submodels/{SubmodelIdentifierContact}/submodel-elements/ContactName/attachment"; + + // Act + var response = await ApiContext.GetAsync(url); + + // Assert + Assert.Equal(404, response.Status); + } + private static async Task ParseResponseRootAsync(Microsoft.Playwright.IAPIResponse response) { var content = await response.TextAsync(); diff --git a/source/AAS.TwinEngine.Plugin.TestPlugin/Example/apiCollection/Aas Repository/Get file attachment by idshort and aasID.bru b/source/AAS.TwinEngine.Plugin.TestPlugin/Example/apiCollection/Aas Repository/Get file attachment by idshort and aasID.bru new file mode 100644 index 00000000..fa32e2b7 --- /dev/null +++ b/source/AAS.TwinEngine.Plugin.TestPlugin/Example/apiCollection/Aas Repository/Get file attachment by idshort and aasID.bru @@ -0,0 +1,22 @@ +meta { + name: Get file attachment by idshort and aasID + type: http + seq: 11 +} + +get { + url: {{DataEngineBaseUrl}}/shells/:aasIdentifier/submodels/:submodelIdentifier/submodel-elements/:idShortPath/attachment + body: none + auth: inherit +} + +params:path { + idShortPath: Documents%255B0%255D.DocumentVersions%255B0%255D.DigitalFiles%255B0%255D + aasIdentifier: {{aasIdentifier}} + submodelIdentifier: {{submodelIdentifierHandoverDocumentation}} +} + +settings { + encodeUrl: true + timeout: 0 +} From 7037b0658285c587f0faa7978b7ea74cd6829700 Mon Sep 17 00:00:00 2001 From: Hardi Shah Date: Fri, 7 Aug 2026 16:30:21 +0530 Subject: [PATCH 09/25] Fix nullability issue by adding null-forgiving operator to AasIdentifier in GetResourceByIdAsync calls --- .../Api/AasRepository/Handler/AasRepositoryHandler.cs | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/source/AAS.TwinEngine.DataEngine/Api/AasRepository/Handler/AasRepositoryHandler.cs b/source/AAS.TwinEngine.DataEngine/Api/AasRepository/Handler/AasRepositoryHandler.cs index 3dbce657..af6e3745 100644 --- a/source/AAS.TwinEngine.DataEngine/Api/AasRepository/Handler/AasRepositoryHandler.cs +++ b/source/AAS.TwinEngine.DataEngine/Api/AasRepository/Handler/AasRepositoryHandler.cs @@ -108,7 +108,7 @@ public Task GetSubmodelElementByAasIdAsync( decodedIdShortPath.ValidateIdShortPath(nameof(request.IdShortPath), logger); return GetResourceByIdAsync( - request.AasIdentifier, + request!.AasIdentifier, request.SubmodelId, "submodel element By AasID", async (aasId, submodelId) => await aasRepositoryService.GetSubmodelElementByAasIdAsync(aasId, submodelId, request?.IdShortPath, cancellationToken).ConfigureAwait(false)); @@ -122,7 +122,7 @@ public Task GetFileByPathByAasIdAsync( decodedIdShortPath.ValidateIdShortPath(nameof(request.IdShortPath), logger); return GetResourceByIdAsync( - request.AasIdentifier, + request!.AasIdentifier, request.SubmodelIdentifier, "file By AasID", async (aasId, submodelId) => await aasRepositoryService.GetFileAttachmentByAasIdAsync(aasId, submodelId, decodedIdShortPath, cancellationToken).ConfigureAwait(false)); From 9fe25eccba1d96642f3df2f1b930d09b530e4ed9 Mon Sep 17 00:00:00 2001 From: Hardi Shah Date: Fri, 7 Aug 2026 17:58:48 +0530 Subject: [PATCH 10/25] Remove local environment configuration and update file attachment URLs in test data --- example/apiCollection/environments/local-net.bru | 3 --- .../Api/AasRepository/Handler/AasRepositoryHandler.cs | 4 ++-- .../TestData/GetAllSubmodelElementsByAasId_Expected.json | 8 ++++---- 3 files changed, 6 insertions(+), 9 deletions(-) delete mode 100644 example/apiCollection/environments/local-net.bru diff --git a/example/apiCollection/environments/local-net.bru b/example/apiCollection/environments/local-net.bru deleted file mode 100644 index 4a42e390..00000000 --- a/example/apiCollection/environments/local-net.bru +++ /dev/null @@ -1,3 +0,0 @@ -vars { - DataEngineBaseUrl: https://localhost:5059/ -} diff --git a/source/AAS.TwinEngine.DataEngine/Api/AasRepository/Handler/AasRepositoryHandler.cs b/source/AAS.TwinEngine.DataEngine/Api/AasRepository/Handler/AasRepositoryHandler.cs index af6e3745..3dbce657 100644 --- a/source/AAS.TwinEngine.DataEngine/Api/AasRepository/Handler/AasRepositoryHandler.cs +++ b/source/AAS.TwinEngine.DataEngine/Api/AasRepository/Handler/AasRepositoryHandler.cs @@ -108,7 +108,7 @@ public Task GetSubmodelElementByAasIdAsync( decodedIdShortPath.ValidateIdShortPath(nameof(request.IdShortPath), logger); return GetResourceByIdAsync( - request!.AasIdentifier, + request.AasIdentifier, request.SubmodelId, "submodel element By AasID", async (aasId, submodelId) => await aasRepositoryService.GetSubmodelElementByAasIdAsync(aasId, submodelId, request?.IdShortPath, cancellationToken).ConfigureAwait(false)); @@ -122,7 +122,7 @@ public Task GetFileByPathByAasIdAsync( decodedIdShortPath.ValidateIdShortPath(nameof(request.IdShortPath), logger); return GetResourceByIdAsync( - request!.AasIdentifier, + request.AasIdentifier, request.SubmodelIdentifier, "file By AasID", async (aasId, submodelId) => await aasRepositoryService.GetFileAttachmentByAasIdAsync(aasId, submodelId, decodedIdShortPath, cancellationToken).ConfigureAwait(false)); diff --git a/source/AAS.TwinEngine.Plugin.TestPlugin.PlaywrightTests/AasRepository/TestData/GetAllSubmodelElementsByAasId_Expected.json b/source/AAS.TwinEngine.Plugin.TestPlugin.PlaywrightTests/AasRepository/TestData/GetAllSubmodelElementsByAasId_Expected.json index 6f50545b..54f840f4 100644 --- a/source/AAS.TwinEngine.Plugin.TestPlugin.PlaywrightTests/AasRepository/TestData/GetAllSubmodelElementsByAasId_Expected.json +++ b/source/AAS.TwinEngine.Plugin.TestPlugin.PlaywrightTests/AasRepository/TestData/GetAllSubmodelElementsByAasId_Expected.json @@ -1667,7 +1667,7 @@ "value": "DigitalFile[\\d{2,3}]" } ], - "value": "https://docs.google.com/viewer?url=https://raw.githubusercontent.com/AAS-TwinEngine/AAS.TwinEngine.DataEngine/refs/heads/main/example/data/dummy_document.pdf", + "value": "https://raw.githubusercontent.com/AAS-TwinEngine/AAS.TwinEngine.DataEngine/refs/heads/main/example/data/dummy_document.pdf", "contentType": "application/pdf", "modelType": "File" } @@ -2656,7 +2656,7 @@ "value": "DigitalFile[\\d{2,3}]" } ], - "value": "https://docs.google.com/viewer?url=https://raw.githubusercontent.com/AAS-TwinEngine/AAS.TwinEngine.DataEngine/refs/heads/main/example/data/dummy_document.pdf", + "value": "https://raw.githubusercontent.com/AAS-TwinEngine/AAS.TwinEngine.DataEngine/refs/heads/main/example/data/dummy_document.pdf", "contentType": "application/pdf", "modelType": "File" } @@ -4425,7 +4425,7 @@ "value": "DigitalFile[\\d{2,3}]" } ], - "value": "https://docs.google.com/viewer?url=https://raw.githubusercontent.com/AAS-TwinEngine/AAS.TwinEngine.DataEngine/refs/heads/main/example/data/dummy_document.pdf", + "value": "https://raw.githubusercontent.com/AAS-TwinEngine/AAS.TwinEngine.DataEngine/refs/heads/main/example/data/dummy_document.pdf", "contentType": "application/pdf", "modelType": "File" } @@ -6194,7 +6194,7 @@ "value": "DigitalFile[\\d{2,3}]" } ], - "value": "https://docs.google.com/viewer?url=https://raw.githubusercontent.com/AAS-TwinEngine/AAS.TwinEngine.DataEngine/refs/heads/main/example/data/dummy_document.pdf", + "value": "https://raw.githubusercontent.com/AAS-TwinEngine/AAS.TwinEngine.DataEngine/refs/heads/main/example/data/dummy_document.pdf", "contentType": "application/pdf", "modelType": "File" } From 60158643bd972c99a02ceca5f5fc0442cd5c553e Mon Sep 17 00:00:00 2001 From: Hardi Shah Date: Mon, 10 Aug 2026 09:27:29 +0530 Subject: [PATCH 11/25] Refactor shell query to use request object pattern Replaced multiple parameters with GetShellsByAssetIdsRequest in controller, handler, and interface for improved maintainability. Updated method calls and logic to use the new request object. Applied null-safe access patterns to other handler methods and cleaned up unused using directives. --- .../AasRepository/AasRepositoryController.cs | 7 ++- .../Handler/AasRepositoryHandler.cs | 47 ++++++++----------- .../Handler/IAasRepositoryHandler.cs | 2 +- .../Requests/GetShellsByAssetIdsRequest.cs | 3 ++ 4 files changed, 27 insertions(+), 32 deletions(-) create mode 100644 source/AAS.TwinEngine.DataEngine/Api/AasRepository/Requests/GetShellsByAssetIdsRequest.cs diff --git a/source/AAS.TwinEngine.DataEngine/Api/AasRepository/AasRepositoryController.cs b/source/AAS.TwinEngine.DataEngine/Api/AasRepository/AasRepositoryController.cs index 50ea1a53..822021d7 100644 --- a/source/AAS.TwinEngine.DataEngine/Api/AasRepository/AasRepositoryController.cs +++ b/source/AAS.TwinEngine.DataEngine/Api/AasRepository/AasRepositoryController.cs @@ -1,12 +1,10 @@ -using System.ComponentModel; -using System.Net; +using System.Net; using System.Text.Json.Nodes; using AAS.TwinEngine.DataEngine.Api.AasRepository.Handler; using AAS.TwinEngine.DataEngine.Api.AasRepository.Requests; using AAS.TwinEngine.DataEngine.Api.AasRepository.Responses; using AAS.TwinEngine.DataEngine.Api.Shared.Results; -using AAS.TwinEngine.DataEngine.Api.SubmodelRepository.Handler; using AAS.TwinEngine.DataEngine.Api.SubmodelRepository.Requests; using AAS.TwinEngine.DataEngine.Api.SubmodelRepository.Responses; using AAS.TwinEngine.DataEngine.ApplicationLogic.Exceptions.Responses; @@ -52,7 +50,8 @@ public async Task> GetShellsByAssetIdAsync( CancellationToken cancellationToken) { logger.LogInformation("Start request to get shells (assetIds/idShort filters)"); - var response = await aasRepositoryHandler.GetShellsByAssetIdsAsync(assetIds, idShort, limit, cursor, cancellationToken).ConfigureAwait(false); + var request = new GetShellsByAssetIdsRequest(assetIds, idShort, limit, cursor); + var response = await aasRepositoryHandler.GetShellsByAssetIdsAsync(request, cancellationToken).ConfigureAwait(false); return Ok(response); } diff --git a/source/AAS.TwinEngine.DataEngine/Api/AasRepository/Handler/AasRepositoryHandler.cs b/source/AAS.TwinEngine.DataEngine/Api/AasRepository/Handler/AasRepositoryHandler.cs index 3dbce657..7ec636c9 100644 --- a/source/AAS.TwinEngine.DataEngine/Api/AasRepository/Handler/AasRepositoryHandler.cs +++ b/source/AAS.TwinEngine.DataEngine/Api/AasRepository/Handler/AasRepositoryHandler.cs @@ -21,24 +21,23 @@ public class AasRepositoryHandler( ILogger logger, IAasRepositoryService aasRepositoryService) : IAasRepositoryHandler { - public async Task GetShellsByAssetIdsAsync( - string[]? assetIds, string? idShort, int? limit, string? cursor, CancellationToken cancellationToken) + public async Task GetShellsByAssetIdsAsync(GetShellsByAssetIdsRequest request, CancellationToken cancellationToken) { - limit.ValidateLimit(logger); - cursor?.ValidateCursor(logger); + request?.Limit.ValidateLimit(logger); + request?.Cursor?.ValidateCursor(logger); - var assetIdsFilters = assetIds is not null && assetIds.Length > 0 - ? AssetIdHelper.DecodeAssetIds(assetIds, logger) + var assetIdsFilters = request?.AssetIds is not null && request.AssetIds.Length > 0 + ? AssetIdHelper.DecodeAssetIds(request.AssetIds, logger) : null; var filter = new ShellSearchFilter { SpecificAssetIds = assetIdsFilters, - IdShort = idShort + IdShort = request?.IdShort }; var shells = await aasRepositoryService - .GetShellsByFiltersAsync(filter, limit, cursor, cancellationToken) + .GetShellsByFiltersAsync(filter, request?.Limit, request?.Cursor, cancellationToken) .ConfigureAwait(false); return shells.ToDto(); @@ -73,57 +72,51 @@ public Task GetSubmodelRefByIdAsync(GetSubmodelRefRequest request, public Task GetSubmodelByAasIdAsync(GetSubmodelByAasRequest request, CancellationToken cancellationToken) { - var queryOptions = new SubmodelQueryOptions(request.Level.ToString(), request.Extent.ToString()); + var queryOptions = new SubmodelQueryOptions(request?.Level.ToString(), request?.Extent.ToString()); return GetResourceByIdAsync( - request.AasIdentifier, - request.SubmodelId, + request?.AasIdentifier, + request?.SubmodelId, "submodel By AasID", async (aasId, submodelId) => await aasRepositoryService.GetSubmodelByAasIdAsync(aasId, submodelId, queryOptions, cancellationToken).ConfigureAwait(false)); } - public Task GetAllSubmodelElementsByAasIdAsync( - GetAllSubmodelElementsByAasRequest request, - CancellationToken cancellationToken) + public Task GetAllSubmodelElementsByAasIdAsync(GetAllSubmodelElementsByAasRequest request, CancellationToken cancellationToken) { request?.Limit.ValidateLimit(logger); request?.Cursor?.ValidateCursor(logger); - var queryOptions = new SubmodelQueryOptions(request.Level.ToString(), request.Extent.ToString()); + var queryOptions = new SubmodelQueryOptions(request?.Level.ToString(), request?.Extent.ToString()); return GetResourceByIdAsync( - request.AasIdentifier, - request.SubmodelId, + request?.AasIdentifier, + request?.SubmodelId, "submodel elements By AasID", async (aasId, submodelId) => (await aasRepositoryService .GetAllSubmodelElementsByAasIdAsync(aasId, submodelId, queryOptions, request?.Limit, request?.Cursor, cancellationToken) .ConfigureAwait(false)).ToDto()); } - public Task GetSubmodelElementByAasIdAsync( - GetSubmodelElementByAasRequest request, - CancellationToken cancellationToken) + public Task GetSubmodelElementByAasIdAsync(GetSubmodelElementByAasRequest request, CancellationToken cancellationToken) { var decodedIdShortPath = Uri.UnescapeDataString(request?.IdShortPath ?? string.Empty); decodedIdShortPath.ValidateIdShortPath(nameof(request.IdShortPath), logger); return GetResourceByIdAsync( - request.AasIdentifier, - request.SubmodelId, + request?.AasIdentifier, + request?.SubmodelId, "submodel element By AasID", async (aasId, submodelId) => await aasRepositoryService.GetSubmodelElementByAasIdAsync(aasId, submodelId, request?.IdShortPath, cancellationToken).ConfigureAwait(false)); } - public Task GetFileByPathByAasIdAsync( - GetFileByPathByAasIdRequest request, - CancellationToken cancellationToken) + public Task GetFileByPathByAasIdAsync(GetFileByPathByAasIdRequest request, CancellationToken cancellationToken) { var decodedIdShortPath = Uri.UnescapeDataString(request?.IdShortPath ?? string.Empty); decodedIdShortPath.ValidateIdShortPath(nameof(request.IdShortPath), logger); return GetResourceByIdAsync( - request.AasIdentifier, - request.SubmodelIdentifier, + request?.AasIdentifier, + request?.SubmodelIdentifier, "file By AasID", async (aasId, submodelId) => await aasRepositoryService.GetFileAttachmentByAasIdAsync(aasId, submodelId, decodedIdShortPath, cancellationToken).ConfigureAwait(false)); } diff --git a/source/AAS.TwinEngine.DataEngine/Api/AasRepository/Handler/IAasRepositoryHandler.cs b/source/AAS.TwinEngine.DataEngine/Api/AasRepository/Handler/IAasRepositoryHandler.cs index a4bab3c9..61a5d23b 100644 --- a/source/AAS.TwinEngine.DataEngine/Api/AasRepository/Handler/IAasRepositoryHandler.cs +++ b/source/AAS.TwinEngine.DataEngine/Api/AasRepository/Handler/IAasRepositoryHandler.cs @@ -11,7 +11,7 @@ namespace AAS.TwinEngine.DataEngine.Api.AasRepository.Handler; public interface IAasRepositoryHandler { - Task GetShellsByAssetIdsAsync(string[]? assetIds, string? idShort, int? limit, string? cursor, CancellationToken cancellationToken); + Task GetShellsByAssetIdsAsync(GetShellsByAssetIdsRequest request, CancellationToken cancellationToken); Task GetShellByIdAsync(GetShellRequest request, CancellationToken cancellationToken); diff --git a/source/AAS.TwinEngine.DataEngine/Api/AasRepository/Requests/GetShellsByAssetIdsRequest.cs b/source/AAS.TwinEngine.DataEngine/Api/AasRepository/Requests/GetShellsByAssetIdsRequest.cs new file mode 100644 index 00000000..c2d2c4d1 --- /dev/null +++ b/source/AAS.TwinEngine.DataEngine/Api/AasRepository/Requests/GetShellsByAssetIdsRequest.cs @@ -0,0 +1,3 @@ +namespace AAS.TwinEngine.DataEngine.Api.AasRepository.Requests; + +public record GetShellsByAssetIdsRequest(string[]? AssetIds, string? IdShort, int? Limit, string? Cursor); From afd978932d9807d440502ab2bc435509d90c631b Mon Sep 17 00:00:00 2001 From: Hardi Shah Date: Mon, 10 Aug 2026 10:50:32 +0530 Subject: [PATCH 12/25] Refactor AasRepositoryControllerTests and AasRepositoryHandlerTests to use GetShellsByAssetIdsRequest object --- .../AasRepositoryControllerTests.cs | 11 ++++++---- .../Handler/AasRepositoryHandlerTests.cs | 20 ++++++++++++------- 2 files changed, 20 insertions(+), 11 deletions(-) diff --git a/source/AAS.TwinEngine.DataEngine.UnitTests/Api/AasRepository/AasRepositoryControllerTests.cs b/source/AAS.TwinEngine.DataEngine.UnitTests/Api/AasRepository/AasRepositoryControllerTests.cs index 60a4654d..3d76ba97 100644 --- a/source/AAS.TwinEngine.DataEngine.UnitTests/Api/AasRepository/AasRepositoryControllerTests.cs +++ b/source/AAS.TwinEngine.DataEngine.UnitTests/Api/AasRepository/AasRepositoryControllerTests.cs @@ -58,7 +58,8 @@ public AasRepositoryControllerTests() public async Task GetShellsByAssetIdAsync_ReturnsOkResult() { var expectedResponse = new ShellsDto { PagingMetaData = new PagingMetaDataDto { Cursor = null }, Result = [] }; - _handler.GetShellsByAssetIdsAsync(Arg.Any(), Arg.Any(), Arg.Any(), Arg.Any(), Arg.Any()) + var request = new GetShellsByAssetIdsRequest(["dGVzdA"], null, null, null); + _handler.GetShellsByAssetIdsAsync(request, Arg.Any()) .Returns(expectedResponse); var result = await _sut.GetShellsByAssetIdAsync(["dGVzdA"], null, null, null, CancellationToken.None); @@ -71,19 +72,21 @@ public async Task GetShellsByAssetIdAsync_WithIdShort_ReturnsOkResult() { var expectedResponse = new ShellsDto { PagingMetaData = new PagingMetaDataDto { Cursor = null }, Result = [] }; const string idShort = "test-idshort"; - _handler.GetShellsByAssetIdsAsync(Arg.Any(), idShort, Arg.Any(), Arg.Any(), Arg.Any()) + _handler.GetShellsByAssetIdsAsync(Arg.Any(), Arg.Any()) .Returns(expectedResponse); var result = await _sut.GetShellsByAssetIdAsync(["dGVzdA"], idShort, null, null, CancellationToken.None); Assert.IsType>(result); - await _handler.Received(1).GetShellsByAssetIdsAsync(Arg.Any(), idShort, Arg.Any(), Arg.Any(), Arg.Any()); + await _handler.Received(1).GetShellsByAssetIdsAsync( + Arg.Is(r => r.IdShort == idShort), + Arg.Any()); } [Fact] public async Task GetShellsByAssetIdAsync_ThrowsException_Propagates() { - _handler.GetShellsByAssetIdsAsync(Arg.Any(), Arg.Any(), Arg.Any(), Arg.Any(), Arg.Any()) + _handler.GetShellsByAssetIdsAsync(Arg.Any(), Arg.Any()) .Throws(new Exception("error")); var exception = await Record.ExceptionAsync(() => _sut.GetShellsByAssetIdAsync(["dGVzdA"], null, null, null, CancellationToken.None)); diff --git a/source/AAS.TwinEngine.DataEngine.UnitTests/Api/AasRepository/Handler/AasRepositoryHandlerTests.cs b/source/AAS.TwinEngine.DataEngine.UnitTests/Api/AasRepository/Handler/AasRepositoryHandlerTests.cs index ea84b200..956a5e3b 100644 --- a/source/AAS.TwinEngine.DataEngine.UnitTests/Api/AasRepository/Handler/AasRepositoryHandlerTests.cs +++ b/source/AAS.TwinEngine.DataEngine.UnitTests/Api/AasRepository/Handler/AasRepositoryHandlerTests.cs @@ -32,8 +32,9 @@ public async Task GetShellsByAssetIdsAsync_WithNullAssetIds_ReturnsAllShells() { _ = _aasRepositoryService.GetShellsByFiltersAsync(Arg.Any(), null, null, Arg.Any()) .Returns(new Shells { PagingMetaData = new PagingMetaData { Cursor = null }, Result = [] }); + var request = new GetShellsByAssetIdsRequest(null, null, null, null); - var result = await _sut.GetShellsByAssetIdsAsync(null, null, null, null, CancellationToken.None); + var result = await _sut.GetShellsByAssetIdsAsync(request, CancellationToken.None); Assert.NotNull(result); await _aasRepositoryService.Received().GetShellsByFiltersAsync(Arg.Is(f => f.SpecificAssetIds == null && f.IdShort == null), null, null, Arg.Any()); @@ -44,8 +45,9 @@ public async Task GetShellsByAssetIdsAsync_WithEmptyAssetIds_ReturnsAllShells() { _ = _aasRepositoryService.GetShellsByFiltersAsync(Arg.Any(), null, null, Arg.Any()) .Returns(new Shells { PagingMetaData = new PagingMetaData { Cursor = null }, Result = [] }); + var request = new GetShellsByAssetIdsRequest([], null, null, null); - var result = await _sut.GetShellsByAssetIdsAsync([], null, null, null, CancellationToken.None); + var result = await _sut.GetShellsByAssetIdsAsync(request, CancellationToken.None); Assert.NotNull(result); await _aasRepositoryService.Received().GetShellsByFiltersAsync(Arg.Is(f => f.SpecificAssetIds == null && f.IdShort == null), null, null, Arg.Any()); @@ -55,9 +57,10 @@ public async Task GetShellsByAssetIdsAsync_WithEmptyAssetIds_ReturnsAllShells() public async Task GetShellsByAssetIdsAsync_WithInvalidBase64Url_ThrowsInvalidUserInputException() { var assetIds = new[] { "not-valid-base64!!!" }; + var request = new GetShellsByAssetIdsRequest(assetIds, null, null, null); await Assert.ThrowsAsync( - () => _sut.GetShellsByAssetIdsAsync(assetIds, null, null, null, CancellationToken.None)); + () => _sut.GetShellsByAssetIdsAsync(request, CancellationToken.None)); } [Fact] @@ -67,7 +70,7 @@ public async Task GetShellsByAssetIdsAsync_WithValidInput_ReturnsShells() var encoded = Convert.ToBase64String(System.Text.Encoding.UTF8.GetBytes(json)) .Replace('+', '-').Replace('/', '_').TrimEnd('='); var assetIds = new[] { encoded }; - + var request = new GetShellsByAssetIdsRequest(assetIds, null, null, null); var shell = new AssetAdministrationShell( "urn:example:aas:001", new AssetInformation(AssetKind.Instance)); @@ -76,7 +79,7 @@ public async Task GetShellsByAssetIdsAsync_WithValidInput_ReturnsShells() Arg.Any(), null, null, Arg.Any()) .Returns(new Shells { PagingMetaData = new PagingMetaData { Cursor = null }, Result = [shell] }); - var result = await _sut.GetShellsByAssetIdsAsync(assetIds, null, null, null, CancellationToken.None); + var result = await _sut.GetShellsByAssetIdsAsync(request, CancellationToken.None); Assert.NotNull(result); Assert.Single(result.Result!); @@ -90,8 +93,10 @@ public async Task GetShellsByAssetIdsAsync_WithNegativeLimit_ThrowsInvalidUserIn .Replace('+', '-').Replace('/', '_').TrimEnd('='); var assetIds = new[] { encoded }; + var request = new GetShellsByAssetIdsRequest(assetIds, null, -1, null); + await Assert.ThrowsAsync( - () => _sut.GetShellsByAssetIdsAsync(assetIds, null, -1, null, CancellationToken.None)); + () => _sut.GetShellsByAssetIdsAsync(request, CancellationToken.None)); } [Fact] @@ -100,8 +105,9 @@ public async Task GetShellsByAssetIdsAsync_WithIdShort_PassesIdShortToService() const string idShort = "test-idshort"; _ = _aasRepositoryService.GetShellsByFiltersAsync(Arg.Any(), null, null, Arg.Any()) .Returns(new Shells { PagingMetaData = new PagingMetaData { Cursor = null }, Result = [] }); + var request = new GetShellsByAssetIdsRequest(null, idShort, null, null); - var result = await _sut.GetShellsByAssetIdsAsync(null, idShort, null, null, CancellationToken.None); + var result = await _sut.GetShellsByAssetIdsAsync(request, CancellationToken.None); Assert.NotNull(result); await _aasRepositoryService.Received().GetShellsByFiltersAsync(Arg.Is(f => f.SpecificAssetIds == null && f.IdShort == idShort), null, null, Arg.Any()); From 699d1d39faeaf2b58be48d83f413edb7e7756e68 Mon Sep 17 00:00:00 2001 From: Hardi Shah Date: Mon, 10 Aug 2026 11:12:11 +0530 Subject: [PATCH 13/25] Improve logging for resource fetch and request start Added detailed logging for resource fetches, including decoded AAS and Submodel IDs. Updated LogRequestStart to log differently for "shell descriptors" by omitting the AAS Identifier, while other resources include it. --- .../Api/AasRegistry/Handler/ShellDescriptorHandler.cs | 7 ++++--- 1 file changed, 4 insertions(+), 3 deletions(-) diff --git a/source/AAS.TwinEngine.DataEngine/Api/AasRegistry/Handler/ShellDescriptorHandler.cs b/source/AAS.TwinEngine.DataEngine/Api/AasRegistry/Handler/ShellDescriptorHandler.cs index 8fb164e0..0eb717c0 100644 --- a/source/AAS.TwinEngine.DataEngine/Api/AasRegistry/Handler/ShellDescriptorHandler.cs +++ b/source/AAS.TwinEngine.DataEngine/Api/AasRegistry/Handler/ShellDescriptorHandler.cs @@ -73,6 +73,7 @@ private async Task GetResourceAsync( { var decodedAasId = encodedAasId?.DecodeBase64Url(logger); var decodedSubmodelId = encodedSubmodelId?.DecodeBase64Url(logger); + logger.LogInformation("Get {ResourceName} for AAS: {AasId}, Submodel: {SubmodelId}", resourceName, decodedAasId, decodedSubmodelId); var result = await fetchFunc(decodedAasId, decodedSubmodelId).ConfigureAwait(false); @@ -97,13 +98,13 @@ private async Task GetResourceAsync( private void LogRequestStart(string resourceName, string? decodedId) { - if (resourceName is not "shell descriptor") + if (resourceName is "shell descriptors") { - logger.LogInformation("Start executing get request for {ResourceName} for AAS Identifier: {AasIdentifier}", resourceName, decodedId); + logger.LogInformation("Start executing get request for {ResourceName}", resourceName); } else { - logger.LogInformation("Start executing get request for {ResourceName}", resourceName); + logger.LogInformation("Start executing get request for {ResourceName} for AAS Identifier: {AasIdentifier}", resourceName, decodedId); } } From 500382b446bfa166d3f3176bc285d30f8962a878 Mon Sep 17 00:00:00 2001 From: Hardi Shah Date: Wed, 12 Aug 2026 09:30:13 +0530 Subject: [PATCH 14/25] enhance handler to use decoded value --- .../Api/AasRepository/Handler/AasRepositoryHandler.cs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/source/AAS.TwinEngine.DataEngine/Api/AasRepository/Handler/AasRepositoryHandler.cs b/source/AAS.TwinEngine.DataEngine/Api/AasRepository/Handler/AasRepositoryHandler.cs index 7ec636c9..30e889ca 100644 --- a/source/AAS.TwinEngine.DataEngine/Api/AasRepository/Handler/AasRepositoryHandler.cs +++ b/source/AAS.TwinEngine.DataEngine/Api/AasRepository/Handler/AasRepositoryHandler.cs @@ -106,7 +106,7 @@ public Task GetSubmodelElementByAasIdAsync(GetSubmodelElementB request?.AasIdentifier, request?.SubmodelId, "submodel element By AasID", - async (aasId, submodelId) => await aasRepositoryService.GetSubmodelElementByAasIdAsync(aasId, submodelId, request?.IdShortPath, cancellationToken).ConfigureAwait(false)); + async (aasId, submodelId) => await aasRepositoryService.GetSubmodelElementByAasIdAsync(aasId, submodelId, decodedIdShortPath, cancellationToken).ConfigureAwait(false)); } public Task GetFileByPathByAasIdAsync(GetFileByPathByAasIdRequest request, CancellationToken cancellationToken) From 1f841cb398ebfc3ead73f5fef5d56204939a4c6e Mon Sep 17 00:00:00 2001 From: Hardi Shah Date: Wed, 12 Aug 2026 10:05:59 +0530 Subject: [PATCH 15/25] Make submodel ID comparison case-sensitive Changed submodel identifier matching from case-insensitive (OrdinalIgnoreCase) to case-sensitive (Ordinal) in ValidateSubmodelBelongsToAasAsync. Submodel IDs must now match exactly, including case. --- .../Services/AasRepository/AasRepositoryService.cs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/source/AAS.TwinEngine.DataEngine/ApplicationLogic/Services/AasRepository/AasRepositoryService.cs b/source/AAS.TwinEngine.DataEngine/ApplicationLogic/Services/AasRepository/AasRepositoryService.cs index 3faf164a..d606e860 100644 --- a/source/AAS.TwinEngine.DataEngine/ApplicationLogic/Services/AasRepository/AasRepositoryService.cs +++ b/source/AAS.TwinEngine.DataEngine/ApplicationLogic/Services/AasRepository/AasRepositoryService.cs @@ -160,7 +160,7 @@ public async Task ValidateSubmodelBelongsToAasAsync(string aasIdentifier, string var submodelRefs = await GetSubmodelRefByIdAsync(aasIdentifier, null, null, cancellationToken).ConfigureAwait(false); var submodelExists = submodelRefs.Result?.SelectMany(r => r.Keys ?? []) - .Any(k => string.Equals(k.Value, submodelIdentifier, StringComparison.OrdinalIgnoreCase)) ?? false; + .Any(k => string.Equals(k.Value, submodelIdentifier, StringComparison.Ordinal)) ?? false; if (!submodelExists) { From 872e75a54748ef483b4969881dd28909f5b351eb Mon Sep 17 00:00:00 2001 From: Hardi Shah Date: Wed, 12 Aug 2026 10:58:46 +0530 Subject: [PATCH 16/25] Enhance Test case --- .../Services/AasRegistry/ShellDescriptorControllerTests.cs | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/source/AAS.TwinEngine.DataEngine.ModuleTests/Api/Services/AasRegistry/ShellDescriptorControllerTests.cs b/source/AAS.TwinEngine.DataEngine.ModuleTests/Api/Services/AasRegistry/ShellDescriptorControllerTests.cs index a32630da..84004e5d 100644 --- a/source/AAS.TwinEngine.DataEngine.ModuleTests/Api/Services/AasRegistry/ShellDescriptorControllerTests.cs +++ b/source/AAS.TwinEngine.DataEngine.ModuleTests/Api/Services/AasRegistry/ShellDescriptorControllerTests.cs @@ -538,7 +538,7 @@ [new Key(KeyTypes.Submodel, "Other")], } [Fact] - public async Task GetSubmodelDescriptorByAasIdAsync_WithDifferentCaseInSubmodelId_ReturnsOkAsync() + public async Task GetSubmodelDescriptorByAasIdAsync_WithDifferentCaseInSubmodelId_ReturnsNotFoundAsync() { // Arrange const string aasIdentifier = "aHR0cHM6Ly9leGFtcGxlLmNvbS9pZHMvYWFzLzExNzBfMTE2MF8zMDUyXzY1Njg="; @@ -565,7 +565,7 @@ [new Key(KeyTypes.Submodel, submodelKey)], var response = await _client.GetAsync($"/shell-descriptors/{aasIdentifier}/submodel-descriptors/{encodedSubmodelId}"); // Assert - Assert.Equal(HttpStatusCode.OK, response.StatusCode); + Assert.Equal(HttpStatusCode.NotFound, response.StatusCode); } private static string EncodeBase64Url(string plainText) From f782cd885aa84e88b91e53f48f2dc4dbe40f9278 Mon Sep 17 00:00:00 2001 From: mm-omdi Date: Thu, 13 Aug 2026 17:39:41 +0530 Subject: [PATCH 17/25] Refactor ShellDescriptorHandler and ShellDescriptorService to remove unused services and improve code clarity --- .../Api/AasRegistry/Handler/ShellDescriptorHandlerTests.cs | 4 +--- .../Api/AasRegistry/Handler/ShellDescriptorHandler.cs | 6 +----- .../Services/AasRegistry/ShellDescriptorService.cs | 4 ++-- 3 files changed, 4 insertions(+), 10 deletions(-) diff --git a/source/AAS.TwinEngine.DataEngine.UnitTests/Api/AasRegistry/Handler/ShellDescriptorHandlerTests.cs b/source/AAS.TwinEngine.DataEngine.UnitTests/Api/AasRegistry/Handler/ShellDescriptorHandlerTests.cs index dc57105c..eec78680 100644 --- a/source/AAS.TwinEngine.DataEngine.UnitTests/Api/AasRegistry/Handler/ShellDescriptorHandlerTests.cs +++ b/source/AAS.TwinEngine.DataEngine.UnitTests/Api/AasRegistry/Handler/ShellDescriptorHandlerTests.cs @@ -25,12 +25,10 @@ namespace AAS.TwinEngine.DataEngine.UnitTests.Api.AasRegistry.Handler; public class ShellDescriptorHandlerTests { private readonly IShellDescriptorService _shellDescriptorService = Substitute.For(); - private readonly IAasRepositoryService _aasRepositoryService = Substitute.For(); - private readonly ISubmodelDescriptorService _submodelDescriptorService = Substitute.For(); private readonly ILogger _logger = Substitute.For>(); private readonly ShellDescriptorHandler _sut; - public ShellDescriptorHandlerTests() => _sut = new ShellDescriptorHandler(_logger, _shellDescriptorService, _aasRepositoryService, _submodelDescriptorService); + public ShellDescriptorHandlerTests() => _sut = new ShellDescriptorHandler(_logger, _shellDescriptorService); [Fact] public async Task GetAllShellDescriptors_ReturnsAllShellDescriptors_WhenExists() diff --git a/source/AAS.TwinEngine.DataEngine/Api/AasRegistry/Handler/ShellDescriptorHandler.cs b/source/AAS.TwinEngine.DataEngine/Api/AasRegistry/Handler/ShellDescriptorHandler.cs index 0eb717c0..ff4397b6 100644 --- a/source/AAS.TwinEngine.DataEngine/Api/AasRegistry/Handler/ShellDescriptorHandler.cs +++ b/source/AAS.TwinEngine.DataEngine/Api/AasRegistry/Handler/ShellDescriptorHandler.cs @@ -6,16 +6,12 @@ using AAS.TwinEngine.DataEngine.ApplicationLogic.Exceptions.Application; using AAS.TwinEngine.DataEngine.ApplicationLogic.Extensions; using AAS.TwinEngine.DataEngine.ApplicationLogic.Services.AasRegistry; -using AAS.TwinEngine.DataEngine.ApplicationLogic.Services.AasRepository; -using AAS.TwinEngine.DataEngine.ApplicationLogic.Services.SubmodelRegistry; namespace AAS.TwinEngine.DataEngine.Api.AasRegistry.Handler; public class ShellDescriptorHandler( ILogger logger, - IShellDescriptorService shellDescriptorService, - IAasRepositoryService aasRepositoryService, - ISubmodelDescriptorService submodelDescriptorService) : IShellDescriptorHandler + IShellDescriptorService shellDescriptorService) : IShellDescriptorHandler { public Task GetAllShellDescriptors(GetShellDescriptorsRequest request, CancellationToken cancellationToken) { diff --git a/source/AAS.TwinEngine.DataEngine/ApplicationLogic/Services/AasRegistry/ShellDescriptorService.cs b/source/AAS.TwinEngine.DataEngine/ApplicationLogic/Services/AasRegistry/ShellDescriptorService.cs index d4712f73..18714350 100644 --- a/source/AAS.TwinEngine.DataEngine/ApplicationLogic/Services/AasRegistry/ShellDescriptorService.cs +++ b/source/AAS.TwinEngine.DataEngine/ApplicationLogic/Services/AasRegistry/ShellDescriptorService.cs @@ -115,11 +115,11 @@ public class ShellDescriptorService( var submodelIds = submodelRefs.Result? .SelectMany(reference => reference.Keys ?? []) .Select(key => key.Value) - .Distinct(StringComparer.OrdinalIgnoreCase) + .Distinct(StringComparer.Ordinal) .ToList() ?? []; var descriptors = (await Task.WhenAll(submodelIds.Select(submodelId => - GetSubmodelDescriptorByAasIdAsync(aasId, submodelId, cancellationToken))).ConfigureAwait(false)) + submodelDescriptorService.GetSubmodelDescriptorByIdAsync(submodelId, cancellationToken))).ConfigureAwait(false)) .OfType() .ToList(); From f033d5d0532392b6d0c075bcce83cc3cddb61aa7 Mon Sep 17 00:00:00 2001 From: mm-omdi Date: Fri, 14 Aug 2026 11:21:30 +0530 Subject: [PATCH 18/25] Add Level and Extent parameters to GetSubmodelElement requests and handlers --- .../AasRepositoryControllerTests.cs | 2 +- .../Handler/SubmodelRepositoryHandlerTests.cs | 26 ++++++++++++++++--- .../SubmodelRepositoryControllerTests.cs | 16 +++++++++++- .../SubmodelRepositoryServiceTests.cs | 16 ++++++------ .../Handler/SubmodelRepositoryHandler.cs | 6 ++++- .../Requests/GetSubmodelElementRequest.cs | 7 ++++- .../SubmodelRepositoryController.cs | 11 ++++++-- .../AasRepository/AasRepositoryService.cs | 2 +- .../ISubmodelRepositoryService.cs | 2 +- .../ISubmodelTemplateService.cs | 2 +- .../SubmodelRepositoryService.cs | 7 +++-- .../SubmodelTemplateService.cs | 4 +-- 12 files changed, 74 insertions(+), 27 deletions(-) diff --git a/source/AAS.TwinEngine.DataEngine.ModuleTests/Api/Services/AasRepository/AasRepositoryControllerTests.cs b/source/AAS.TwinEngine.DataEngine.ModuleTests/Api/Services/AasRepository/AasRepositoryControllerTests.cs index 5300c9be..1a425932 100644 --- a/source/AAS.TwinEngine.DataEngine.ModuleTests/Api/Services/AasRepository/AasRepositoryControllerTests.cs +++ b/source/AAS.TwinEngine.DataEngine.ModuleTests/Api/Services/AasRepository/AasRepositoryControllerTests.cs @@ -724,7 +724,7 @@ public async Task GetSubmodelElementByAasIdAsync_ReturnsOkAsync() .Returns([new Reference(ReferenceTypes.ModelReference, [new Key(KeyTypes.Submodel, submodelKey)], null)]); _ = _mockSubmodelRepositoryService - .GetSubmodelElementAsync(requestedSubmodelId, IdShortPath, Arg.Any()) + .GetSubmodelElementAsync(requestedSubmodelId, IdShortPath, Arg.Any(), Arg.Any()) .Returns(new Property(DataTypeDefXsd.String) { IdShort = IdShortPath }); // Act diff --git a/source/AAS.TwinEngine.DataEngine.UnitTests/Api/SubmodelRepository/Handler/SubmodelRepositoryHandlerTests.cs b/source/AAS.TwinEngine.DataEngine.UnitTests/Api/SubmodelRepository/Handler/SubmodelRepositoryHandlerTests.cs index 76ddad78..c5d8e157 100644 --- a/source/AAS.TwinEngine.DataEngine.UnitTests/Api/SubmodelRepository/Handler/SubmodelRepositoryHandlerTests.cs +++ b/source/AAS.TwinEngine.DataEngine.UnitTests/Api/SubmodelRepository/Handler/SubmodelRepositoryHandlerTests.cs @@ -107,12 +107,12 @@ public async Task HandleSubmodelElement_ReturnsSubmodel_WhenSubmodelElementExist var encodedId = WebEncoders.Base64UrlEncode(Encoding.UTF8.GetBytes(SubmodelId)); var request = new GetSubmodelElementRequest(encodedId, IdShortPath); var submodelElement = Substitute.For(); - _submodelRepository.GetSubmodelElementAsync(SubmodelId, IdShortPath, Arg.Any()).Returns(submodelElement); + _submodelRepository.GetSubmodelElementAsync(SubmodelId, IdShortPath, Arg.Any(), Arg.Any()).Returns(submodelElement); var result = await _sut.GetSubmodelElement(request, CancellationToken.None); Assert.Equal(submodelElement, result); - await _submodelRepository.Received().GetSubmodelElementAsync(Arg.Any(), Arg.Any(), Arg.Any()); + await _submodelRepository.Received().GetSubmodelElementAsync(Arg.Any(), Arg.Any(), Arg.Any(), Arg.Any()); } [Fact] @@ -122,11 +122,29 @@ public async Task HandleSubmodelElement_SubmodelIsNull_ThrowsSubmodelNotFoundExc var encodedId = WebEncoders.Base64UrlEncode(Encoding.UTF8.GetBytes(SubmodelId)); const string IdShortPath = "Segments.LinkedSegment"; var request = new GetSubmodelElementRequest(encodedId, IdShortPath); - _submodelRepository.GetSubmodelElementAsync(SubmodelId, IdShortPath, Arg.Any()).Returns((ISubmodelElement)null!); + _submodelRepository.GetSubmodelElementAsync(SubmodelId, IdShortPath, Arg.Any(), Arg.Any()).Returns((ISubmodelElement)null!); await Assert.ThrowsAsync(() => _sut.GetSubmodelElement(request, CancellationToken.None)); } + [Fact] + public async Task HandleSubmodelElement_WithLevelAndExtent_PassesQueryOptionsToService() + { + const string SubmodelId = "NameplateSubmodel"; + const string IdShortPath = "Segments.LinkedSegment"; + var encodedId = WebEncoders.Base64UrlEncode(Encoding.UTF8.GetBytes(SubmodelId)); + var request = new GetSubmodelElementRequest(encodedId, IdShortPath) { Level = Level.deep, Extent = Extent.withBlobValue }; + var submodelElement = Substitute.For(); + _submodelRepository + .GetSubmodelElementAsync(SubmodelId, IdShortPath, Arg.Is(q => q != null), Arg.Any()) + .Returns(submodelElement); + + await _sut.GetSubmodelElement(request, CancellationToken.None); + + await _submodelRepository.Received(1) + .GetSubmodelElementAsync(SubmodelId, IdShortPath, Arg.Is(q => q != null), Arg.Any()); + } + [Fact] public async Task HandleSubmodelElement_InvalidBase64SubmodelId_ThrowsInternalDataProcessingException() { @@ -255,7 +273,7 @@ public async Task HandleSubmodelElement_ValidIdShortPath_DoesNotThrow(string val var encodedId = SubmodelId.EncodeBase64Url(); var request = new GetSubmodelElementRequest(encodedId, validIdShortPath); var submodelElement = Substitute.For(); - _submodelRepository.GetSubmodelElementAsync(SubmodelId, validIdShortPath, Arg.Any()).Returns(submodelElement); + _submodelRepository.GetSubmodelElementAsync(SubmodelId, validIdShortPath, Arg.Any(), Arg.Any()).Returns(submodelElement); var result = await _sut.GetSubmodelElement(request, CancellationToken.None); diff --git a/source/AAS.TwinEngine.DataEngine.UnitTests/Api/SubmodelRepository/SubmodelRepositoryControllerTests.cs b/source/AAS.TwinEngine.DataEngine.UnitTests/Api/SubmodelRepository/SubmodelRepositoryControllerTests.cs index 89c78fef..c53a32f5 100644 --- a/source/AAS.TwinEngine.DataEngine.UnitTests/Api/SubmodelRepository/SubmodelRepositoryControllerTests.cs +++ b/source/AAS.TwinEngine.DataEngine.UnitTests/Api/SubmodelRepository/SubmodelRepositoryControllerTests.cs @@ -94,13 +94,27 @@ public async Task GetSubmodelElementAsync_ReturnsOkResult_WithJsonObject() _handler.GetSubmodelElement(Arg.Any(), Arg.Any()) .Returns(_expectedElement); - var result = await _sut.GetSubmodelElementAsync(encodedId, _idShortPath, CancellationToken.None); + var result = await _sut.GetSubmodelElementAsync(encodedId, _idShortPath, null, null, CancellationToken.None); var okResult = Assert.IsType(result.Result); var json = Assert.IsType(okResult.Value); Assert.Equal(expectedJson.ToJsonString(), json.ToJsonString()); } + [Fact] + public async Task GetSubmodelElementAsync_WithLevelAndExtent_PassesThemToHandler() + { + var encodedId = WebEncoders.Base64UrlEncode(Encoding.UTF8.GetBytes(_submodelId)); + _handler.GetSubmodelElement(Arg.Any(), Arg.Any()) + .Returns(_expectedElement); + + await _sut.GetSubmodelElementAsync(encodedId, _idShortPath, Level.deep, Extent.withBlobValue, CancellationToken.None); + + await _handler.Received(1).GetSubmodelElement( + Arg.Is(r => r.Level == Level.deep && r.Extent == Extent.withBlobValue), + Arg.Any()); + } + [Fact] public async Task GetAllSubmodelsAsync_ReturnsOkResult_WithSubmodelsDto() { diff --git a/source/AAS.TwinEngine.DataEngine.UnitTests/ApplicationLogic/Services/SubmodelRepository/SubmodelRepositoryServiceTests.cs b/source/AAS.TwinEngine.DataEngine.UnitTests/ApplicationLogic/Services/SubmodelRepository/SubmodelRepositoryServiceTests.cs index c6fff199..2e0ae9df 100644 --- a/source/AAS.TwinEngine.DataEngine.UnitTests/ApplicationLogic/Services/SubmodelRepository/SubmodelRepositoryServiceTests.cs +++ b/source/AAS.TwinEngine.DataEngine.UnitTests/ApplicationLogic/Services/SubmodelRepository/SubmodelRepositoryServiceTests.cs @@ -130,7 +130,7 @@ public async Task GetSubmodelElementAsync_ReturnsFilledSubmodelElement() var expected = TestData.CreateFilledContactInformation(); _templateService - .GetSubmodelTemplateAsync(SubmodelId, IdShortPath, Arg.Any()) + .GetSubmodelTemplateAsync(SubmodelId, IdShortPath, Arg.Any(), Arg.Any()) .Returns(submodel); var semanticTree = CreateSubmodelTreeNode(""); @@ -142,7 +142,7 @@ public async Task GetSubmodelElementAsync_ReturnsFilledSubmodelElement() .Returns(filledSubmodel); _semanticIdHandler.Extract(filledSubmodel, IdShortPath).Returns(expected); - var result = await _sut.GetSubmodelElementAsync(SubmodelId, IdShortPath, CancellationToken.None) as SubmodelElementCollection; + var result = await _sut.GetSubmodelElementAsync(SubmodelId, IdShortPath, null, CancellationToken.None) as SubmodelElementCollection; Assert.Equal(expected.SemanticId, result?.SemanticId); Assert.Equal(expected.Value, result?.Value); @@ -157,7 +157,7 @@ public async Task GetSubmodelElementAsync_IdShortWithIndex_ReturnsFilledSubmodel var expected = TestData.CreateFilledContactName(); _templateService - .GetSubmodelTemplateAsync(SubmodelId, IdShortPathWithNestedElement, Arg.Any()) + .GetSubmodelTemplateAsync(SubmodelId, IdShortPathWithNestedElement, Arg.Any(), Arg.Any()) .Returns(submodel); var semanticTree = CreateSubmodelTreeNode(""); @@ -169,7 +169,7 @@ public async Task GetSubmodelElementAsync_IdShortWithIndex_ReturnsFilledSubmodel .Returns(filledSubmodel); _semanticIdHandler.Extract(filledSubmodel, IdShortPathWithNestedElement).Returns(expected); - var result = await _sut.GetSubmodelElementAsync(SubmodelId, IdShortPathWithNestedElement, CancellationToken.None) as Property; + var result = await _sut.GetSubmodelElementAsync(SubmodelId, IdShortPathWithNestedElement, null, CancellationToken.None) as Property; Assert.Equal(expected.SemanticId, result?.SemanticId); Assert.Equal(expected.Value, result?.Value); @@ -212,11 +212,11 @@ await Assert.ThrowsAsync(() => public async Task GetSubmodelElementAsync_WhenResourceNotFound_ThrowsSubmodelElementNotFoundException() { _templateService - .GetSubmodelTemplateAsync(SubmodelId, IdShortPath, Arg.Any()) + .GetSubmodelTemplateAsync(SubmodelId, IdShortPath, Arg.Any(), Arg.Any()) .ThrowsAsync(new ResourceNotFoundException()); await Assert.ThrowsAsync(() => - _sut.GetSubmodelElementAsync(SubmodelId, IdShortPath, CancellationToken.None)); + _sut.GetSubmodelElementAsync(SubmodelId, IdShortPath, null, CancellationToken.None)); } [Fact] @@ -227,7 +227,7 @@ public async Task GetSubmodelElementAsync_WhenResponseParsingFails_ThrowsInterna .ThrowsAsync(new ResponseParsingException()); await Assert.ThrowsAsync(() => - _sut.GetSubmodelElementAsync(SubmodelId, IdShortPath, CancellationToken.None)); + _sut.GetSubmodelElementAsync(SubmodelId, IdShortPath, null, CancellationToken.None)); } [Fact] @@ -238,7 +238,7 @@ public async Task GetSubmodelElementAsync_WhenRequestTimesOut_ThrowsPluginNotAva .ThrowsAsync(new RequestTimeoutException()); await Assert.ThrowsAsync(() => - _sut.GetSubmodelElementAsync(SubmodelId, IdShortPath, CancellationToken.None)); + _sut.GetSubmodelElementAsync(SubmodelId, IdShortPath, null, CancellationToken.None)); } public static SemanticBranchNode CreateSubmodelTreeNode(string value) diff --git a/source/AAS.TwinEngine.DataEngine/Api/SubmodelRepository/Handler/SubmodelRepositoryHandler.cs b/source/AAS.TwinEngine.DataEngine/Api/SubmodelRepository/Handler/SubmodelRepositoryHandler.cs index 46547301..193e7181 100644 --- a/source/AAS.TwinEngine.DataEngine/Api/SubmodelRepository/Handler/SubmodelRepositoryHandler.cs +++ b/source/AAS.TwinEngine.DataEngine/Api/SubmodelRepository/Handler/SubmodelRepositoryHandler.cs @@ -30,10 +30,14 @@ public Task GetSubmodelElement(GetSubmodelElementRequest reque var decodedIdShortPath = Uri.UnescapeDataString(request?.IdShortPath ?? string.Empty); decodedIdShortPath.ValidateIdShortPath(nameof(request.IdShortPath), logger); + var queryOptions = request?.Level is not null || request?.Extent is not null + ? new SubmodelQueryOptions(request.Level?.ToString(), request.Extent?.ToString()) + : null; + return GetResourceByIdAsync( request?.SubmodelId, "submodel element", - id => submodelRepositoryService.GetSubmodelElementAsync(id, decodedIdShortPath, cancellationToken)!); + id => submodelRepositoryService.GetSubmodelElementAsync(id, decodedIdShortPath, queryOptions, cancellationToken)!); } public async Task GetAllSubmodels(GetAllSubmodelsRequest request, CancellationToken cancellationToken) diff --git a/source/AAS.TwinEngine.DataEngine/Api/SubmodelRepository/Requests/GetSubmodelElementRequest.cs b/source/AAS.TwinEngine.DataEngine/Api/SubmodelRepository/Requests/GetSubmodelElementRequest.cs index 582f56e2..b7a5c322 100644 --- a/source/AAS.TwinEngine.DataEngine/Api/SubmodelRepository/Requests/GetSubmodelElementRequest.cs +++ b/source/AAS.TwinEngine.DataEngine/Api/SubmodelRepository/Requests/GetSubmodelElementRequest.cs @@ -1,3 +1,8 @@ namespace AAS.TwinEngine.DataEngine.Api.SubmodelRepository.Requests; -public record GetSubmodelElementRequest(string SubmodelId, string IdShortPath); +public record GetSubmodelElementRequest(string SubmodelId, string IdShortPath) +{ + public Level? Level { get; set; } + + public Extent? Extent { get; set; } +} diff --git a/source/AAS.TwinEngine.DataEngine/Api/SubmodelRepository/SubmodelRepositoryController.cs b/source/AAS.TwinEngine.DataEngine/Api/SubmodelRepository/SubmodelRepositoryController.cs index ce34db61..3d75e841 100644 --- a/source/AAS.TwinEngine.DataEngine/Api/SubmodelRepository/SubmodelRepositoryController.cs +++ b/source/AAS.TwinEngine.DataEngine/Api/SubmodelRepository/SubmodelRepositoryController.cs @@ -136,6 +136,8 @@ public async Task> GetAllSubmodelElementsAsync /// /// The Submodel's unique id (UTF8-BASE64-URL-encoded) /// The IdShort path to the submodel element (dot-separated) + /// Determines the structural depth of the returned content. Accepted values: deep, core. + /// Controls how blob values are serialized. Accepted values: withBlobValue, withoutBlobValue. /// Requested submodel element /// Bad Request, e.g.the request parameters of the format of the request body is wrong. /// Not Found @@ -145,10 +147,15 @@ public async Task> GetAllSubmodelElementsAsync [ProducesResponseType(typeof(ServiceErrorResponse), (int)HttpStatusCode.BadRequest)] [ProducesResponseType(typeof(ServiceErrorResponse), (int)HttpStatusCode.NotFound)] [ProducesResponseType(typeof(ServiceErrorResponse), (int)HttpStatusCode.InternalServerError)] - public async Task> GetSubmodelElementAsync([FromRoute] string submodelIdentifier, [FromRoute] string idShortPath, CancellationToken cancellationToken) + public async Task> GetSubmodelElementAsync( + [FromRoute] string submodelIdentifier, + [FromRoute] string idShortPath, + [FromQuery] Level? level, + [FromQuery] Extent? extent, + CancellationToken cancellationToken) { logger.LogInformation("Get Submodel Element"); - var request = new GetSubmodelElementRequest(submodelIdentifier, idShortPath); + var request = new GetSubmodelElementRequest(submodelIdentifier, idShortPath) { Level = level, Extent = extent }; var response = await submodelRepositoryHandler.GetSubmodelElement(request, cancellationToken).ConfigureAwait(false); return Ok(Jsonization.Serialize.ToJsonObject(response)); } diff --git a/source/AAS.TwinEngine.DataEngine/ApplicationLogic/Services/AasRepository/AasRepositoryService.cs b/source/AAS.TwinEngine.DataEngine/ApplicationLogic/Services/AasRepository/AasRepositoryService.cs index 3b12400f..fa2996db 100644 --- a/source/AAS.TwinEngine.DataEngine/ApplicationLogic/Services/AasRepository/AasRepositoryService.cs +++ b/source/AAS.TwinEngine.DataEngine/ApplicationLogic/Services/AasRepository/AasRepositoryService.cs @@ -198,7 +198,7 @@ public async Task GetSubmodelElementByAasIdAsync(string aasIde { await ValidateSubmodelBelongsToAasAsync(aasIdentifier, submodelIdentifier, cancellationToken).ConfigureAwait(false); - return await submodelRepositoryService.GetSubmodelElementAsync(submodelIdentifier, idShortPath, cancellationToken).ConfigureAwait(false); + return await submodelRepositoryService.GetSubmodelElementAsync(submodelIdentifier, idShortPath, null, cancellationToken).ConfigureAwait(false); } public async Task GetFileAttachmentByAasIdAsync(string aasIdentifier, string submodelId, string idShortPath, CancellationToken cancellationToken) diff --git a/source/AAS.TwinEngine.DataEngine/ApplicationLogic/Services/SubmodelRepository/ISubmodelRepositoryService.cs b/source/AAS.TwinEngine.DataEngine/ApplicationLogic/Services/SubmodelRepository/ISubmodelRepositoryService.cs index e6e59720..d53145f6 100644 --- a/source/AAS.TwinEngine.DataEngine/ApplicationLogic/Services/SubmodelRepository/ISubmodelRepositoryService.cs +++ b/source/AAS.TwinEngine.DataEngine/ApplicationLogic/Services/SubmodelRepository/ISubmodelRepositoryService.cs @@ -9,7 +9,7 @@ public interface ISubmodelRepositoryService { Task GetSubmodelAsync(string submodelId, SubmodelQueryOptions? queryOptions, CancellationToken cancellationToken); - Task GetSubmodelElementAsync(string submodelId, string idShortPath, CancellationToken cancellationToken); + Task GetSubmodelElementAsync(string submodelId, string idShortPath, SubmodelQueryOptions? queryOptions, CancellationToken cancellationToken); Task GetAllSubmodelsAsync( SubmodelSearchFilter? filter, diff --git a/source/AAS.TwinEngine.DataEngine/ApplicationLogic/Services/SubmodelRepository/ISubmodelTemplateService.cs b/source/AAS.TwinEngine.DataEngine/ApplicationLogic/Services/SubmodelRepository/ISubmodelTemplateService.cs index c4938323..fa6f2855 100644 --- a/source/AAS.TwinEngine.DataEngine/ApplicationLogic/Services/SubmodelRepository/ISubmodelTemplateService.cs +++ b/source/AAS.TwinEngine.DataEngine/ApplicationLogic/Services/SubmodelRepository/ISubmodelTemplateService.cs @@ -9,7 +9,7 @@ public interface ISubmodelTemplateService { Task GetSubmodelTemplateAsync(string submodelId, CancellationToken cancellationToken); - Task GetSubmodelTemplateAsync(string submodelId, string idShortPath, CancellationToken cancellationToken); + Task GetSubmodelTemplateAsync(string submodelId, string idShortPath, SubmodelQueryOptions? queryOptions, CancellationToken cancellationToken); Task GetFilteredSubmodelTemplateAsync(string submodelId, string filteredTemplateId, SubmodelQueryOptions? queryOptions, CancellationToken cancellationToken); diff --git a/source/AAS.TwinEngine.DataEngine/ApplicationLogic/Services/SubmodelRepository/SubmodelRepositoryService.cs b/source/AAS.TwinEngine.DataEngine/ApplicationLogic/Services/SubmodelRepository/SubmodelRepositoryService.cs index f12f4257..cfa30d7e 100644 --- a/source/AAS.TwinEngine.DataEngine/ApplicationLogic/Services/SubmodelRepository/SubmodelRepositoryService.cs +++ b/source/AAS.TwinEngine.DataEngine/ApplicationLogic/Services/SubmodelRepository/SubmodelRepositoryService.cs @@ -52,11 +52,11 @@ public async Task GetSubmodelAsync(string submodelId, SubmodelQueryOp }, ex => new SubmodelNotFoundException(ex)).ConfigureAwait(false); } - public async Task GetSubmodelElementAsync(string submodelId, string idShortPath, CancellationToken cancellationToken) + public async Task GetSubmodelElementAsync(string submodelId, string idShortPath, SubmodelQueryOptions? queryOptions, CancellationToken cancellationToken) { return await ExecuteWithExceptionHandlingAsync(async () => { - var reducedSubmodelTemplate = await submodelTemplateService.GetSubmodelTemplateAsync(submodelId, idShortPath, cancellationToken).ConfigureAwait(false); + var reducedSubmodelTemplate = await submodelTemplateService.GetSubmodelTemplateAsync(submodelId, idShortPath, queryOptions, cancellationToken).ConfigureAwait(false); var submodelWithValues = await BuildSubmodelWithValuesAsync(reducedSubmodelTemplate, submodelId, cancellationToken).ConfigureAwait(false); @@ -64,7 +64,6 @@ public async Task GetSubmodelElementAsync(string submodelId, s }, ex => new SubmodelElementNotFoundException(ex)).ConfigureAwait(false); } - public async Task GetAllSubmodelsAsync(SubmodelSearchFilter? filter, SubmodelQueryOptions? queryOptions, int? limit, string? cursor, CancellationToken cancellationToken) { return await ExecuteWithExceptionHandlingAsync(async () => @@ -254,7 +253,7 @@ public async Task GetFileAttachmentAsync(string submodelId private async Task GetFileElementAsync(string submodelId, string idShortPath, CancellationToken cancellationToken) { - var element = await GetSubmodelElementAsync(submodelId, idShortPath, cancellationToken); + var element = await GetSubmodelElementAsync(submodelId, idShortPath, null, cancellationToken); return GetFileElement(element, idShortPath); } diff --git a/source/AAS.TwinEngine.DataEngine/ApplicationLogic/Services/SubmodelRepository/SubmodelTemplateService.cs b/source/AAS.TwinEngine.DataEngine/ApplicationLogic/Services/SubmodelRepository/SubmodelTemplateService.cs index d79f3287..7625ce2b 100644 --- a/source/AAS.TwinEngine.DataEngine/ApplicationLogic/Services/SubmodelRepository/SubmodelTemplateService.cs +++ b/source/AAS.TwinEngine.DataEngine/ApplicationLogic/Services/SubmodelRepository/SubmodelTemplateService.cs @@ -50,7 +50,7 @@ public async Task GetSubmodelTemplateAsync(string submodelId, Cancell } } - public async Task GetSubmodelTemplateAsync(string submodelId, string idShortPath, CancellationToken cancellationToken) + public async Task GetSubmodelTemplateAsync(string submodelId, string idShortPath, SubmodelQueryOptions? queryOptions, CancellationToken cancellationToken) { if (!string.IsNullOrWhiteSpace(idShortPath)) { @@ -59,7 +59,7 @@ public async Task GetSubmodelTemplateAsync(string submodelId, string ValidateSubmodelId(submodelId); var templateId = _submodelTemplateMappingProvider.GetTemplateId(submodelId); - var submodel = await _templateProvider.GetFilteredSubmodelTemplateAsync(templateId!, null, cancellationToken).ConfigureAwait(false); + var submodel = await _templateProvider.GetFilteredSubmodelTemplateAsync(templateId!, queryOptions, cancellationToken).ConfigureAwait(false); return BuildSubmodel(submodel, idShortPath); } From deb8148804966780e3acf9c2159b268c7db8fec0 Mon Sep 17 00:00:00 2001 From: mm-omdi Date: Mon, 17 Aug 2026 11:28:18 +0530 Subject: [PATCH 19/25] Refactor AAS and Submodel repository to use updated query options and improve parameter handling --- example/basyx/aas-env.properties | 12 +++---- example/docker-compose.yml | 12 +++---- .../Handler/AasRepositoryHandler.cs | 3 +- .../GetSubmodelElementByAasRequest.cs | 2 +- .../Handler/SubmodelRepositoryHandler.cs | 8 ++--- .../Requests/GetSubmodelElementRequest.cs | 7 +--- .../Requests/GetSubmodelRequest.cs | 9 +---- .../SubmodelRepositoryController.cs | 33 ++++++++++--------- .../AasRepository/AasRepositoryService.cs | 4 +-- .../AasRepository/IAasRepositoryService.cs | 2 +- .../SemanticId/ElementHandlers/BlobHandler.cs | 4 +-- 11 files changed, 43 insertions(+), 53 deletions(-) diff --git a/example/basyx/aas-env.properties b/example/basyx/aas-env.properties index 70ef7e28..7bd1c243 100644 --- a/example/basyx/aas-env.properties +++ b/example/basyx/aas-env.properties @@ -7,10 +7,10 @@ basyx.submodelrepository.feature.registryintegration=http://sm-template-registry spring.servlet.multipart.max-file-size=128MB spring.servlet.multipart.max-request-size=128MB basyx.backend=MongoDB -spring.data.mongodb.host=mongo -spring.data.mongodb.port=27017 -spring.data.mongodb.database=aasenvironment -spring.data.mongodb.authentication-database=admin -spring.data.mongodb.username=mongoAdmin -spring.data.mongodb.password=mongoPassword +spring.mongodb.host=mongo +spring.mongodb.port=27017 +spring.mongodb.database=aasenvironment +spring.mongodb.authentication-database=admin +spring.mongodb.username=mongoAdmin +spring.mongodb.password=mongoPassword diff --git a/example/docker-compose.yml b/example/docker-compose.yml index 7187fe20..41c85af7 100644 --- a/example/docker-compose.yml +++ b/example/docker-compose.yml @@ -145,7 +145,7 @@ services: - twinengine-network template-repository: - image: eclipsebasyx/aas-environment:2.0.0-milestone-11 + image: eclipsebasyx/aas-environment:2.0.0-milestone-15 container_name: template-repository volumes: - ./aas:/application/aas @@ -162,28 +162,28 @@ services: - twinengine-network aas-template-registry: - image: eclipsebasyx/aas-registry-log-mongodb:2.0.0-milestone-11 + image: eclipsebasyx/aas-registry-log-mongodb:2.0.0-milestone-15 container_name: aas-template-registry restart: always depends_on: - mongo environment: - - SPRING_DATA_MONGODB_URI=mongodb://mongoAdmin:mongoPassword@mongo:27017 + - SPRING_MONGODB_URI=mongodb://mongoAdmin:mongoPassword@mongo:27017 networks: - twinengine-network sm-template-registry: - image: eclipsebasyx/submodel-registry-log-mongodb:2.0.0-milestone-11 + image: eclipsebasyx/submodel-registry-log-mongodb:2.0.0-milestone-15 container_name: sm-template-registry depends_on: - mongo environment: - - SPRING_DATA_MONGODB_URI=mongodb://mongoAdmin:mongoPassword@mongo:27017 + - SPRING_MONGODB_URI=mongodb://mongoAdmin:mongoPassword@mongo:27017 networks: - twinengine-network aas-web-ui: - image: eclipsebasyx/aas-gui:v2-260526 + image: eclipsebasyx/aas-gui:v2-260801 container_name: aas-ui volumes: - ./logo:/usr/src/app/dist/Logo diff --git a/source/AAS.TwinEngine.DataEngine/Api/AasRepository/Handler/AasRepositoryHandler.cs b/source/AAS.TwinEngine.DataEngine/Api/AasRepository/Handler/AasRepositoryHandler.cs index 16c18ebe..276fcddd 100644 --- a/source/AAS.TwinEngine.DataEngine/Api/AasRepository/Handler/AasRepositoryHandler.cs +++ b/source/AAS.TwinEngine.DataEngine/Api/AasRepository/Handler/AasRepositoryHandler.cs @@ -107,12 +107,13 @@ public Task GetSubmodelElementByAasIdAsync(GetSubmodelElementB { var decodedIdShortPath = Uri.UnescapeDataString(request?.IdShortPath ?? string.Empty); decodedIdShortPath.ValidateIdShortPath(nameof(request.IdShortPath), logger); + var queryOptions = new SubmodelQueryOptions(request?.Level.ToString(), request?.Extent.ToString()); return GetResourceByIdAsync( request?.AasIdentifier, request?.SubmodelId, "submodel element By AasID", - async (aasId, submodelId) => await aasRepositoryService.GetSubmodelElementByAasIdAsync(aasId, submodelId, decodedIdShortPath, cancellationToken).ConfigureAwait(false)); + async (aasId, submodelId) => await aasRepositoryService.GetSubmodelElementByAasIdAsync(aasId, submodelId, decodedIdShortPath, queryOptions, cancellationToken).ConfigureAwait(false)); } public Task GetFileByPathByAasIdAsync(GetFileByPathByAasIdRequest request, CancellationToken cancellationToken) diff --git a/source/AAS.TwinEngine.DataEngine/Api/AasRepository/Requests/GetSubmodelElementByAasRequest.cs b/source/AAS.TwinEngine.DataEngine/Api/AasRepository/Requests/GetSubmodelElementByAasRequest.cs index ae1796f1..16c5fd5f 100644 --- a/source/AAS.TwinEngine.DataEngine/Api/AasRepository/Requests/GetSubmodelElementByAasRequest.cs +++ b/source/AAS.TwinEngine.DataEngine/Api/AasRepository/Requests/GetSubmodelElementByAasRequest.cs @@ -2,4 +2,4 @@ namespace AAS.TwinEngine.DataEngine.Api.AasRepository.Requests; -public record GetSubmodelElementByAasRequest(string AasIdentifier, string SubmodelId, string IdShortPath, Level level, Extent extent); +public record GetSubmodelElementByAasRequest(string AasIdentifier, string SubmodelId, string IdShortPath, Level Level, Extent Extent); diff --git a/source/AAS.TwinEngine.DataEngine/Api/SubmodelRepository/Handler/SubmodelRepositoryHandler.cs b/source/AAS.TwinEngine.DataEngine/Api/SubmodelRepository/Handler/SubmodelRepositoryHandler.cs index 193e7181..f007b72e 100644 --- a/source/AAS.TwinEngine.DataEngine/Api/SubmodelRepository/Handler/SubmodelRepositoryHandler.cs +++ b/source/AAS.TwinEngine.DataEngine/Api/SubmodelRepository/Handler/SubmodelRepositoryHandler.cs @@ -17,10 +17,10 @@ public class SubmodelRepositoryHandler( { public Task GetSubmodel(GetSubmodelRequest request, CancellationToken cancellationToken) { - var queryOptions = request?.Level is not null || request?.Extent is not null ? new SubmodelQueryOptions(request.Level?.ToString(), request.Extent?.ToString()) : null; + var queryOptions = request?.level is not null || request?.extent is not null ? new SubmodelQueryOptions(request.level?.ToString(), request.extent?.ToString()) : null; return GetResourceByIdAsync( - request?.SubmodelId, + request?.submodelId, "submodel", id => submodelRepositoryService.GetSubmodelAsync(id, queryOptions, cancellationToken)!); } @@ -30,8 +30,8 @@ public Task GetSubmodelElement(GetSubmodelElementRequest reque var decodedIdShortPath = Uri.UnescapeDataString(request?.IdShortPath ?? string.Empty); decodedIdShortPath.ValidateIdShortPath(nameof(request.IdShortPath), logger); - var queryOptions = request?.Level is not null || request?.Extent is not null - ? new SubmodelQueryOptions(request.Level?.ToString(), request.Extent?.ToString()) + var queryOptions = request?.level is not null || request?.extent is not null + ? new SubmodelQueryOptions(request.level?.ToString(), request.extent?.ToString()) : null; return GetResourceByIdAsync( diff --git a/source/AAS.TwinEngine.DataEngine/Api/SubmodelRepository/Requests/GetSubmodelElementRequest.cs b/source/AAS.TwinEngine.DataEngine/Api/SubmodelRepository/Requests/GetSubmodelElementRequest.cs index b7a5c322..b170eafb 100644 --- a/source/AAS.TwinEngine.DataEngine/Api/SubmodelRepository/Requests/GetSubmodelElementRequest.cs +++ b/source/AAS.TwinEngine.DataEngine/Api/SubmodelRepository/Requests/GetSubmodelElementRequest.cs @@ -1,8 +1,3 @@ namespace AAS.TwinEngine.DataEngine.Api.SubmodelRepository.Requests; -public record GetSubmodelElementRequest(string SubmodelId, string IdShortPath) -{ - public Level? Level { get; set; } - - public Extent? Extent { get; set; } -} +public record GetSubmodelElementRequest(string SubmodelId, string IdShortPath, Level? level = Level.deep, Extent? extent = Extent.withoutBlobValue); \ No newline at end of file diff --git a/source/AAS.TwinEngine.DataEngine/Api/SubmodelRepository/Requests/GetSubmodelRequest.cs b/source/AAS.TwinEngine.DataEngine/Api/SubmodelRepository/Requests/GetSubmodelRequest.cs index 35f00e87..1ce5ad27 100644 --- a/source/AAS.TwinEngine.DataEngine/Api/SubmodelRepository/Requests/GetSubmodelRequest.cs +++ b/source/AAS.TwinEngine.DataEngine/Api/SubmodelRepository/Requests/GetSubmodelRequest.cs @@ -1,10 +1,3 @@ namespace AAS.TwinEngine.DataEngine.Api.SubmodelRepository.Requests; -public record GetSubmodelRequest(string submodelId) -{ - public string SubmodelId { get; } = submodelId; - - public Level? Level { get; set; } - - public Extent? Extent { get; set; } -} +public record GetSubmodelRequest(string submodelId, Level? level = Level.deep, Extent? extent = Extent.withoutBlobValue); diff --git a/source/AAS.TwinEngine.DataEngine/Api/SubmodelRepository/SubmodelRepositoryController.cs b/source/AAS.TwinEngine.DataEngine/Api/SubmodelRepository/SubmodelRepositoryController.cs index 3d75e841..a5aae4d4 100644 --- a/source/AAS.TwinEngine.DataEngine/Api/SubmodelRepository/SubmodelRepositoryController.cs +++ b/source/AAS.TwinEngine.DataEngine/Api/SubmodelRepository/SubmodelRepositoryController.cs @@ -34,8 +34,8 @@ public class SubmodelRepositoryController( /// The Asset Administration Shell's IdShort. /// The maximum number of elements in the response array. /// A server-generated identifier retrieved from pagingMetadata. - /// Determines the structural depth of the returned content. Accepted values: deep, core. - /// Determines the serialization of the returned content. Accepted values: withBlobValue, withoutBlobValue. + /// Determines the structural depth of the returned content. + /// Determines the serialization of the returned content. /// Returns the requested Submodels. /// Bad Request, e.g. the request parameters or request format are invalid. /// Not Found. @@ -50,9 +50,9 @@ public async Task> GetAllSubmodelsAsync( [FromQuery] string? idShort, [FromQuery] int? limit, [FromQuery] string? cursor, - [FromQuery] Level? level, - [FromQuery] Extent? extent, - CancellationToken cancellationToken) + CancellationToken cancellationToken, + [FromQuery] Level level = Level.deep, + [FromQuery] Extent extent = Extent.withoutBlobValue) { logger.LogInformation("Get All Submodels"); @@ -77,8 +77,8 @@ public async Task> GetAllSubmodelsAsync( /// Returns a specific Submodel. /// /// The Submodel's unique id (UTF8-BASE64-URL-encoded) - /// Determines the structural depth of the returned content. Accepted values: deep, core. - /// Controls how blob values are serialized. Accepted values: withBlobValue, withoutBlobValue. + /// Determines the structural depth of the returned content. + /// Controls how blob values are serialized. /// Requested Submodel /// Bad Request, e.g.the request parameters of the format of the request body is wrong. /// Not Found @@ -95,7 +95,7 @@ public async Task> GetSubmodelAsync( CancellationToken cancellationToken) { logger.LogInformation("Get Submodel"); - var request = new GetSubmodelRequest(submodelIdentifier) { Level = level, Extent = extent }; + var request = new GetSubmodelRequest(submodelIdentifier, level, extent); var response = await submodelRepositoryHandler.GetSubmodel(request, cancellationToken).ConfigureAwait(false); return Ok(Jsonization.Serialize.ToJsonObject(response)); } @@ -106,8 +106,8 @@ public async Task> GetSubmodelAsync( /// The Submodel's unique id (UTF8-BASE64-URL-encoded) /// The maximum number of elements in the response array. /// A server-generated identifier retrieved from pagingMetadata that specifies from which position the result listing should continue. - /// Determines the structural depth of the returned content. Accepted values: deep, core. - /// Determines the serialization of the returned content. Accepted values: withBlobValue, withoutBlobValue. + /// Determines the structural depth of the returned content. + /// Determines the serialization of the returned content. /// List of found submodel elements /// Bad Request, e.g. the request parameters or request format are invalid. /// Not Found @@ -121,9 +121,10 @@ public async Task> GetAllSubmodelElementsAsync [FromRoute] string submodelIdentifier, [FromQuery] int? limit, [FromQuery] string? cursor, - [FromQuery] Level? level, - [FromQuery] Extent? extent, - CancellationToken cancellationToken) + CancellationToken cancellationToken, + [FromQuery] Level level = Level.deep, + [FromQuery] Extent extent = Extent.withoutBlobValue + ) { logger.LogInformation("Get All Submodel Elements"); var request = new GetAllSubmodelElementsRequest(submodelIdentifier, limit, cursor, level, extent); @@ -136,8 +137,8 @@ public async Task> GetAllSubmodelElementsAsync /// /// The Submodel's unique id (UTF8-BASE64-URL-encoded) /// The IdShort path to the submodel element (dot-separated) - /// Determines the structural depth of the returned content. Accepted values: deep, core. - /// Controls how blob values are serialized. Accepted values: withBlobValue, withoutBlobValue. + /// Determines the structural depth of the returned content. + /// Controls how blob values are serialized. /// Requested submodel element /// Bad Request, e.g.the request parameters of the format of the request body is wrong. /// Not Found @@ -155,7 +156,7 @@ public async Task> GetSubmodelElementAsync( CancellationToken cancellationToken) { logger.LogInformation("Get Submodel Element"); - var request = new GetSubmodelElementRequest(submodelIdentifier, idShortPath) { Level = level, Extent = extent }; + var request = new GetSubmodelElementRequest(submodelIdentifier, idShortPath, level, extent); var response = await submodelRepositoryHandler.GetSubmodelElement(request, cancellationToken).ConfigureAwait(false); return Ok(Jsonization.Serialize.ToJsonObject(response)); } diff --git a/source/AAS.TwinEngine.DataEngine/ApplicationLogic/Services/AasRepository/AasRepositoryService.cs b/source/AAS.TwinEngine.DataEngine/ApplicationLogic/Services/AasRepository/AasRepositoryService.cs index fa2996db..15deadaf 100644 --- a/source/AAS.TwinEngine.DataEngine/ApplicationLogic/Services/AasRepository/AasRepositoryService.cs +++ b/source/AAS.TwinEngine.DataEngine/ApplicationLogic/Services/AasRepository/AasRepositoryService.cs @@ -194,11 +194,11 @@ public async Task GetAllSubmodelElementsByAasIdAsync(strin return await submodelRepositoryService.GetAllSubmodelElementsAsync(submodelIdentifier, queryOptions, limit, cursor, cancellationToken).ConfigureAwait(false); } - public async Task GetSubmodelElementByAasIdAsync(string aasIdentifier, string submodelIdentifier, string idShortPath, CancellationToken cancellationToken) + public async Task GetSubmodelElementByAasIdAsync(string aasIdentifier, string submodelIdentifier, string idShortPath, SubmodelQueryOptions? queryOptions, CancellationToken cancellationToken) { await ValidateSubmodelBelongsToAasAsync(aasIdentifier, submodelIdentifier, cancellationToken).ConfigureAwait(false); - return await submodelRepositoryService.GetSubmodelElementAsync(submodelIdentifier, idShortPath, null, cancellationToken).ConfigureAwait(false); + return await submodelRepositoryService.GetSubmodelElementAsync(submodelIdentifier, idShortPath, queryOptions, cancellationToken).ConfigureAwait(false); } public async Task GetFileAttachmentByAasIdAsync(string aasIdentifier, string submodelId, string idShortPath, CancellationToken cancellationToken) diff --git a/source/AAS.TwinEngine.DataEngine/ApplicationLogic/Services/AasRepository/IAasRepositoryService.cs b/source/AAS.TwinEngine.DataEngine/ApplicationLogic/Services/AasRepository/IAasRepositoryService.cs index a135a270..a0b012a2 100644 --- a/source/AAS.TwinEngine.DataEngine/ApplicationLogic/Services/AasRepository/IAasRepositoryService.cs +++ b/source/AAS.TwinEngine.DataEngine/ApplicationLogic/Services/AasRepository/IAasRepositoryService.cs @@ -22,7 +22,7 @@ public interface IAasRepositoryService Task GetAllSubmodelElementsByAasIdAsync(string aasIdentifier, string submodelIdentifier, SubmodelQueryOptions queryOptions, int? limit, string? cursor, CancellationToken cancellationToken); - Task GetSubmodelElementByAasIdAsync(string aasIdentifier, string submodelIdentifier, string idShortPath, CancellationToken cancellationToken); + Task GetSubmodelElementByAasIdAsync(string aasIdentifier, string submodelIdentifier, string idShortPath, SubmodelQueryOptions queryOptions, CancellationToken cancellationToken); Task GetFileAttachmentByAasIdAsync(string aasIdentifier, string submodelId, string idShortPath, CancellationToken cancellationToken); diff --git a/source/AAS.TwinEngine.DataEngine/ApplicationLogic/Services/SubmodelRepository/SemanticId/ElementHandlers/BlobHandler.cs b/source/AAS.TwinEngine.DataEngine/ApplicationLogic/Services/SubmodelRepository/SemanticId/ElementHandlers/BlobHandler.cs index 34f00255..acfbba9a 100644 --- a/source/AAS.TwinEngine.DataEngine/ApplicationLogic/Services/SubmodelRepository/SemanticId/ElementHandlers/BlobHandler.cs +++ b/source/AAS.TwinEngine.DataEngine/ApplicationLogic/Services/SubmodelRepository/SemanticId/ElementHandlers/BlobHandler.cs @@ -17,9 +17,9 @@ public class BlobHandler(ISemanticIdResolver semanticIdResolver) : ISubmodelElem public void FillOut(ISubmodelElement element, SemanticTreeNode values, Action, SemanticTreeNode, bool> fillOutChildren) { - if (values is SemanticLeafNode leafValueNode) + if (values is SemanticLeafNode leafValueNode && element is Blob { Value: not null } blob) { - ((Blob)element).Value = Convert.FromBase64String(leafValueNode.Value); + blob.Value = Convert.FromBase64String(leafValueNode.Value); } } } From b17f1e465760360b3fdc7504b45abb760afae10f Mon Sep 17 00:00:00 2001 From: mm-omdi Date: Mon, 17 Aug 2026 11:43:22 +0530 Subject: [PATCH 20/25] Update GetSubmodel and GetSubmodelTemplate methods to use SubmodelQueryOptions for improved parameter handling --- .../Handler/AasRepositoryHandlerTests.cs | 6 ++-- .../Handler/SubmodelRepositoryHandlerTests.cs | 20 +++++++++---- .../SubmodelRepositoryControllerTests.cs | 10 +++---- .../ElementHandlers/BlobHandlerTests.cs | 14 +++++++++- .../SubmodelRepositoryServiceTests.cs | 4 +-- .../SubmodelTemplateServiceTests.cs | 28 +++++++++---------- 6 files changed, 52 insertions(+), 30 deletions(-) diff --git a/source/AAS.TwinEngine.DataEngine.UnitTests/Api/AasRepository/Handler/AasRepositoryHandlerTests.cs b/source/AAS.TwinEngine.DataEngine.UnitTests/Api/AasRepository/Handler/AasRepositoryHandlerTests.cs index 8ac97446..7d0a190b 100644 --- a/source/AAS.TwinEngine.DataEngine.UnitTests/Api/AasRepository/Handler/AasRepositoryHandlerTests.cs +++ b/source/AAS.TwinEngine.DataEngine.UnitTests/Api/AasRepository/Handler/AasRepositoryHandlerTests.cs @@ -457,14 +457,14 @@ public async Task GetSubmodelElementByAasIdAsync_ReturnsElement_WhenSubmodelBelo Level.deep, Extent.withoutBlobValue); var expectedElement = new Property(idShort: IdShortPath, valueType: DataTypeDefXsd.String); - _aasRepositoryService.GetSubmodelElementByAasIdAsync(AasId, SubmodelId, IdShortPath, Arg.Any()) + _aasRepositoryService.GetSubmodelElementByAasIdAsync(AasId, SubmodelId, IdShortPath, Arg.Any(), Arg.Any()) .Returns(expectedElement); var result = await _sut.GetSubmodelElementByAasIdAsync(request, CancellationToken.None); Assert.IsType(result); await _aasRepositoryService.Received(1) - .GetSubmodelElementByAasIdAsync(AasId, SubmodelId, IdShortPath, Arg.Any()); + .GetSubmodelElementByAasIdAsync(AasId, SubmodelId, IdShortPath, Arg.Any(), Arg.Any()); } [Fact] @@ -479,7 +479,7 @@ public async Task GetSubmodelElementByAasIdAsync_SubmodelNotInAas_ThrowsSubmodel idShortPath, Level.deep, Extent.withoutBlobValue); - _aasRepositoryService.GetSubmodelElementByAasIdAsync(AasId, SubmodelId, idShortPath, Arg.Any()) + _aasRepositoryService.GetSubmodelElementByAasIdAsync(AasId, SubmodelId, idShortPath, Arg.Any(), Arg.Any()) .ThrowsAsync(new SubmodelNotFoundException(SubmodelId)); await Assert.ThrowsAsync( diff --git a/source/AAS.TwinEngine.DataEngine.UnitTests/Api/SubmodelRepository/Handler/SubmodelRepositoryHandlerTests.cs b/source/AAS.TwinEngine.DataEngine.UnitTests/Api/SubmodelRepository/Handler/SubmodelRepositoryHandlerTests.cs index c5d8e157..858113cb 100644 --- a/source/AAS.TwinEngine.DataEngine.UnitTests/Api/SubmodelRepository/Handler/SubmodelRepositoryHandlerTests.cs +++ b/source/AAS.TwinEngine.DataEngine.UnitTests/Api/SubmodelRepository/Handler/SubmodelRepositoryHandlerTests.cs @@ -49,7 +49,7 @@ public async Task HandleSubmodel_WithLevelAndExtent_PassesQueryOptionsToService( { const string SubmodelId = "NameplateSubmodel"; var encodedId = WebEncoders.Base64UrlEncode(Encoding.UTF8.GetBytes(SubmodelId)); - var request = new GetSubmodelRequest(encodedId) { Level = Level.deep, Extent = Extent.withBlobValue }; + var request = new GetSubmodelRequest(encodedId, Level.deep, Extent.withBlobValue); var expectedSubmodel = Substitute.For(); _submodelRepository .GetSubmodelAsync(SubmodelId, Arg.Is(q => q != null), Arg.Any()) @@ -62,19 +62,29 @@ await _submodelRepository.Received(1) } [Fact] - public async Task HandleSubmodel_WithNoLevelOrExtent_PassesNullQueryOptionsToService() + public async Task HandleSubmodel_WithDefaultLevelAndExtent_PassesQueryOptionsToService() { const string SubmodelId = "NameplateSubmodel"; var encodedId = WebEncoders.Base64UrlEncode(Encoding.UTF8.GetBytes(SubmodelId)); var request = new GetSubmodelRequest(encodedId); _submodelRepository - .GetSubmodelAsync(SubmodelId, (SubmodelQueryOptions?)null, Arg.Any()) + .GetSubmodelAsync( + SubmodelId, + Arg.Is(q => q != null + && q.Level == Level.deep.ToString() + && q.Extent == Extent.withoutBlobValue.ToString()), + Arg.Any()) .Returns(Substitute.For()); await _sut.GetSubmodel(request, CancellationToken.None); await _submodelRepository.Received(1) - .GetSubmodelAsync(SubmodelId, (SubmodelQueryOptions?)null, Arg.Any()); + .GetSubmodelAsync( + SubmodelId, + Arg.Is(q => q != null + && q.Level == Level.deep.ToString() + && q.Extent == Extent.withoutBlobValue.ToString()), + Arg.Any()); } [Fact] @@ -133,7 +143,7 @@ public async Task HandleSubmodelElement_WithLevelAndExtent_PassesQueryOptionsToS const string SubmodelId = "NameplateSubmodel"; const string IdShortPath = "Segments.LinkedSegment"; var encodedId = WebEncoders.Base64UrlEncode(Encoding.UTF8.GetBytes(SubmodelId)); - var request = new GetSubmodelElementRequest(encodedId, IdShortPath) { Level = Level.deep, Extent = Extent.withBlobValue }; + var request = new GetSubmodelElementRequest(encodedId, IdShortPath, Level.deep, Extent.withBlobValue); var submodelElement = Substitute.For(); _submodelRepository .GetSubmodelElementAsync(SubmodelId, IdShortPath, Arg.Is(q => q != null), Arg.Any()) diff --git a/source/AAS.TwinEngine.DataEngine.UnitTests/Api/SubmodelRepository/SubmodelRepositoryControllerTests.cs b/source/AAS.TwinEngine.DataEngine.UnitTests/Api/SubmodelRepository/SubmodelRepositoryControllerTests.cs index c53a32f5..6f84c64d 100644 --- a/source/AAS.TwinEngine.DataEngine.UnitTests/Api/SubmodelRepository/SubmodelRepositoryControllerTests.cs +++ b/source/AAS.TwinEngine.DataEngine.UnitTests/Api/SubmodelRepository/SubmodelRepositoryControllerTests.cs @@ -81,8 +81,8 @@ await _sut.GetSubmodelAsync(encodedId, AAS.TwinEngine.DataEngine.Api.SubmodelRep AAS.TwinEngine.DataEngine.Api.SubmodelRepository.Requests.Extent.withBlobValue, CancellationToken.None); await _handler.Received(1).GetSubmodel( - Arg.Is(r => r.Level == AAS.TwinEngine.DataEngine.Api.SubmodelRepository.Requests.Level.deep - && r.Extent == AAS.TwinEngine.DataEngine.Api.SubmodelRepository.Requests.Extent.withBlobValue), + Arg.Is(r => r.level == AAS.TwinEngine.DataEngine.Api.SubmodelRepository.Requests.Level.deep + && r.extent == AAS.TwinEngine.DataEngine.Api.SubmodelRepository.Requests.Extent.withBlobValue), Arg.Any()); } @@ -111,7 +111,7 @@ public async Task GetSubmodelElementAsync_WithLevelAndExtent_PassesThemToHandler await _sut.GetSubmodelElementAsync(encodedId, _idShortPath, Level.deep, Extent.withBlobValue, CancellationToken.None); await _handler.Received(1).GetSubmodelElement( - Arg.Is(r => r.Level == Level.deep && r.Extent == Extent.withBlobValue), + Arg.Is(r => r.level == Level.deep && r.extent == Extent.withBlobValue), Arg.Any()); } @@ -126,7 +126,7 @@ public async Task GetAllSubmodelsAsync_ReturnsOkResult_WithSubmodelsDto() _handler.GetAllSubmodels(Arg.Any(), Arg.Any()) .Returns(expectedDto); - var result = await _sut.GetAllSubmodelsAsync(null, null, null, null, null, null, CancellationToken.None); + var result = await _sut.GetAllSubmodelsAsync(null, null, null, null, CancellationToken.None); var okResult = Assert.IsType(result.Result); var dto = Assert.IsType(okResult.Value); @@ -144,7 +144,7 @@ public async Task GetAllSubmodelsAsync_PassesQueryParamsToHandler() .Returns(expectedDto); var request = new GetAllSubmodelsRequest { SemanticId = SemanticId, IdShort = IdShort, Limit = Limit }; - await _sut.GetAllSubmodelsAsync(SemanticId, IdShort, Limit, null, null, null, CancellationToken.None); + await _sut.GetAllSubmodelsAsync(SemanticId, IdShort, Limit, null, CancellationToken.None); await _handler.Received(1).GetAllSubmodels( Arg.Is(r => r.SemanticId == SemanticId && r.IdShort == IdShort && r.Limit == Limit), diff --git a/source/AAS.TwinEngine.DataEngine.UnitTests/ApplicationLogic/Services/SubmodelRepository/SemanticId/ElementHandlers/BlobHandlerTests.cs b/source/AAS.TwinEngine.DataEngine.UnitTests/ApplicationLogic/Services/SubmodelRepository/SemanticId/ElementHandlers/BlobHandlerTests.cs index d603e26b..7bc0ba14 100644 --- a/source/AAS.TwinEngine.DataEngine.UnitTests/ApplicationLogic/Services/SubmodelRepository/SemanticId/ElementHandlers/BlobHandlerTests.cs +++ b/source/AAS.TwinEngine.DataEngine.UnitTests/ApplicationLogic/Services/SubmodelRepository/SemanticId/ElementHandlers/BlobHandlerTests.cs @@ -55,7 +55,7 @@ public void Extract_ReturnsLeafNode() [Fact] public void FillOut_WithLeafNode_SetsBase64Value() { - var blob = new Blob(contentType: "image/png", idShort: "MyBlob"); + var blob = new Blob(contentType: "image/png", idShort: "MyBlob", value: [9]); var base64 = Convert.ToBase64String(new byte[] { 1, 2, 3 }); var values = new SemanticLeafNode("http://test/blob", base64, DataType.String, Cardinality.One); @@ -65,6 +65,18 @@ public void FillOut_WithLeafNode_SetsBase64Value() Equal([1, 2, 3], blob.Value); } + [Fact] + public void FillOut_WithLeafNodeAndNoTemplateValue_LeavesValueUnset() + { + var blob = new Blob(contentType: "image/png", idShort: "MyBlob"); + var base64 = Convert.ToBase64String(new byte[] { 1, 2, 3 }); + var values = new SemanticLeafNode("http://test/blob", base64, DataType.String, Cardinality.One); + + _sut.FillOut(blob, values, (_, _, _) => { }); + + Null(blob.Value); + } + [Fact] public void FillOut_WithBranchNode_DoesNotModifyValue() { diff --git a/source/AAS.TwinEngine.DataEngine.UnitTests/ApplicationLogic/Services/SubmodelRepository/SubmodelRepositoryServiceTests.cs b/source/AAS.TwinEngine.DataEngine.UnitTests/ApplicationLogic/Services/SubmodelRepository/SubmodelRepositoryServiceTests.cs index 2e0ae9df..1653bad4 100644 --- a/source/AAS.TwinEngine.DataEngine.UnitTests/ApplicationLogic/Services/SubmodelRepository/SubmodelRepositoryServiceTests.cs +++ b/source/AAS.TwinEngine.DataEngine.UnitTests/ApplicationLogic/Services/SubmodelRepository/SubmodelRepositoryServiceTests.cs @@ -594,7 +594,7 @@ await Assert.ThrowsAsync(() => private void ArrangeAttachmentElement(string idShortPath, ISubmodelElement element) { var template = TestData.CreateSubmodelWithElement(element, idShortPath); - _templateService.GetSubmodelTemplateAsync(SubmodelId, idShortPath, Arg.Any()).Returns(template); + _templateService.GetSubmodelTemplateAsync(SubmodelId, idShortPath, Arg.Any(), Arg.Any()).Returns(template); _semanticIdHandler.Extract(Arg.Any()).Returns(CreateSubmodelTreeNode("")); _pluginDataHandler.TryGetValuesAsync(Arg.Any>(), Arg.Any(), SubmodelId, Arg.Any()).Returns(CreateSubmodelTreeNode("")); _semanticIdHandler.FillOutTemplate(Arg.Any(), Arg.Any()).Returns(template); @@ -644,7 +644,7 @@ public async Task GetFileAttachmentAsync_WhenSubmodelNotFound_ThrowsSubmodelElem const string IdShortPath = "Documents.ProductImage"; _templateService - .GetSubmodelTemplateAsync(SubmodelId, IdShortPath, Arg.Any()) + .GetSubmodelTemplateAsync(SubmodelId, IdShortPath, Arg.Any(), Arg.Any()) .ThrowsAsync(new ResourceNotFoundException()); await Assert.ThrowsAsync(() => diff --git a/source/AAS.TwinEngine.DataEngine.UnitTests/ApplicationLogic/Services/SubmodelRepository/SubmodelTemplateServiceTests.cs b/source/AAS.TwinEngine.DataEngine.UnitTests/ApplicationLogic/Services/SubmodelRepository/SubmodelTemplateServiceTests.cs index f022df4f..11d29ca9 100644 --- a/source/AAS.TwinEngine.DataEngine.UnitTests/ApplicationLogic/Services/SubmodelRepository/SubmodelTemplateServiceTests.cs +++ b/source/AAS.TwinEngine.DataEngine.UnitTests/ApplicationLogic/Services/SubmodelRepository/SubmodelTemplateServiceTests.cs @@ -74,7 +74,7 @@ public async Task GetSubmodelTemplateAsync_ReturnsNull_WhenElementNotFound() _templateProvider.GetFilteredSubmodelTemplateAsync(TemplateId, null, Arg.Any()) .Returns(expectedSubmodel); - var exception = await Assert.ThrowsAsync(() => _sut.GetSubmodelTemplateAsync(SubmodelId, IdShortPath, CancellationToken.None)); + var exception = await Assert.ThrowsAsync(() => _sut.GetSubmodelTemplateAsync(SubmodelId, IdShortPath, null, CancellationToken.None)); Assert.Equal("Submodel Element not found.", exception.Message); } @@ -83,7 +83,7 @@ public async Task GetSubmodelTemplateAsync_ThrowsException_WhenSubmodelIdIsInval { string? submodelId = null; - var exception = await Assert.ThrowsAsync(() => _sut.GetSubmodelTemplateAsync(submodelId!, "idShort", CancellationToken.None)); + var exception = await Assert.ThrowsAsync(() => _sut.GetSubmodelTemplateAsync(submodelId!, "idShort", null, CancellationToken.None)); Assert.Equal("Internal Server Error.", exception.Message); } @@ -97,7 +97,7 @@ public async Task GetSubmodelTemplateAsync_ReturnsElement_WhenSingleProperty() _templateProvider.GetFilteredSubmodelTemplateAsync(TemplateId, null, Arg.Any()) .Returns(expectedSubmodel); - var result = await _sut.GetSubmodelTemplateAsync(SubmodelId, IdShortPath, CancellationToken.None); + var result = await _sut.GetSubmodelTemplateAsync(SubmodelId, IdShortPath, null, CancellationToken.None); Assert.Equal(GetSemanticId(expectedElement), GetSemanticId(result)); Assert.Equal(expectedElement.SubmodelElements!.Count, result.SubmodelElements!.Count); Assert.Single(expectedElement.SubmodelElements); @@ -113,7 +113,7 @@ public async Task GetSubmodelTemplateAsync_ReturnsCustomSubmodel_WhenNestedPrope _templateProvider.GetFilteredSubmodelTemplateAsync(TemplateId, null, Arg.Any()) .Returns(expectedSubmodel); - var result = await _sut.GetSubmodelTemplateAsync(SubmodelId, IdShortPath, CancellationToken.None); + var result = await _sut.GetSubmodelTemplateAsync(SubmodelId, IdShortPath, null, CancellationToken.None); Assert.Equal(GetSemanticId(expectedElement), GetSemanticId(result)); Assert.Equal(expectedElement.SubmodelElements!.Count, result.SubmodelElements!.Count); @@ -131,7 +131,7 @@ public async Task GetSubmodelTemplateAsync_ReturnsSubmodelElementNotFoundExcepti _templateProvider.GetFilteredSubmodelTemplateAsync(TemplateId, null, Arg.Any()) .Returns(expectedSubmodel); - var exception = await Assert.ThrowsAsync(() => _sut.GetSubmodelTemplateAsync(SubmodelId, IdShortPath, CancellationToken.None)); + var exception = await Assert.ThrowsAsync(() => _sut.GetSubmodelTemplateAsync(SubmodelId, IdShortPath, null, CancellationToken.None)); } [Fact] @@ -139,7 +139,7 @@ public async Task GetSubmodelElementTemplateAsync_ThrowsBadRequestException_When { const string IdShortPath = "ContactInformation0"; - var exception = await Assert.ThrowsAsync(() => _sut.GetSubmodelTemplateAsync("", IdShortPath, CancellationToken.None)); + var exception = await Assert.ThrowsAsync(() => _sut.GetSubmodelTemplateAsync("", IdShortPath, null, CancellationToken.None)); Assert.IsType(exception); } @@ -152,7 +152,7 @@ public async Task GetSubmodelTemplateAsync_ReturnsSubmodel_WhenPathContainsListI _templateProvider.GetFilteredSubmodelTemplateAsync(TemplateId, null, Arg.Any()) .Returns(expectedSubmodel); - var result = await _sut.GetSubmodelTemplateAsync(SubmodelId, path, CancellationToken.None); + var result = await _sut.GetSubmodelTemplateAsync(SubmodelId, path, null, CancellationToken.None); Assert.Equal(GetSemanticId(expectedSubmodel), GetSemanticId(result)); @@ -176,7 +176,7 @@ public async Task GetSubmodelTemplateAsync_WithListIndexPath_ReturnsSubmodelWith _templateProvider.GetFilteredSubmodelTemplateAsync(TemplateId, null, Arg.Any()) .Returns(expectedSubmodel); - var result = await _sut.GetSubmodelTemplateAsync(SubmodelId, Path, CancellationToken.None); + var result = await _sut.GetSubmodelTemplateAsync(SubmodelId, Path, null, CancellationToken.None); Assert.Equal(GetSemanticId(expectedSubmodel), GetSemanticId(result)); @@ -200,7 +200,7 @@ public async Task GetSubmodelTemplateAsync_Supports_UrlEncoded_ListIndex() _templateProvider.GetFilteredSubmodelTemplateAsync(TemplateId, null, Arg.Any()) .Returns(submodel); - var result = await _sut.GetSubmodelTemplateAsync(SubmodelId, Path, CancellationToken.None); + var result = await _sut.GetSubmodelTemplateAsync(SubmodelId, Path, null, CancellationToken.None); Assert.NotNull(result); } @@ -216,7 +216,7 @@ public async Task GetSubmodelTemplateAsync_Throws_When_ListIndex_IsNegative() .Returns(submodel); await Assert.ThrowsAsync( - () => _sut.GetSubmodelTemplateAsync(SubmodelId, Path, CancellationToken.None)); + () => _sut.GetSubmodelTemplateAsync(SubmodelId, Path, null, CancellationToken.None)); } [Fact] @@ -229,7 +229,7 @@ public async Task GetSubmodelTemplateAsync_ReturnsSubmodel_WhenTypeValueListElem _templateProvider.GetFilteredSubmodelTemplateAsync(TemplateId, null, Arg.Any()) .Returns(submodel); - var result = await _sut.GetSubmodelTemplateAsync(SubmodelId, Path, CancellationToken.None); + var result = await _sut.GetSubmodelTemplateAsync(SubmodelId, Path, null, CancellationToken.None); Assert.Equal(GetSemanticId(expectedSubmodel), GetSemanticId(result)); @@ -253,7 +253,7 @@ public async Task GetSubmodelTemplateAsync_ThrowsInternalDataProcessingException _templateProvider.GetFilteredSubmodelTemplateAsync(TemplateId, null, Arg.Any()) .Returns(submodel); - await Assert.ThrowsAsync(() => _sut.GetSubmodelTemplateAsync(SubmodelId, Path, CancellationToken.None)); + await Assert.ThrowsAsync(() => _sut.GetSubmodelTemplateAsync(SubmodelId, Path, null, CancellationToken.None)); } [Fact] @@ -265,7 +265,7 @@ public async Task GetSubmodelTemplateAsync_ThrowsNotFoundException_WhenPathSegme _templateProvider.GetFilteredSubmodelTemplateAsync(TemplateId, null, Arg.Any()) .Returns(submodel); - await Assert.ThrowsAsync(() => _sut.GetSubmodelTemplateAsync(SubmodelId, Path, CancellationToken.None)); + await Assert.ThrowsAsync(() => _sut.GetSubmodelTemplateAsync(SubmodelId, Path, null, CancellationToken.None)); } [Fact] @@ -322,7 +322,7 @@ public async Task GetSubmodelTemplateAsync_WithIdShortPath_ThrowsSubmodelElement .ThrowsAsync(new ResourceNotFoundException()); await Assert.ThrowsAsync( - () => _sut.GetSubmodelTemplateAsync(SubmodelId, "SomePath", CancellationToken.None)); + () => _sut.GetSubmodelTemplateAsync(SubmodelId, "SomePath", null, CancellationToken.None)); } #region GetFilteredSubmodelTemplateAsync From f350b019af4b3703b5fb511385e993b01b1d88f7 Mon Sep 17 00:00:00 2001 From: mm-omdi Date: Mon, 17 Aug 2026 12:05:02 +0530 Subject: [PATCH 21/25] Refactor Submodel repository methods to utilize SubmodelQueryOptions for improved parameter handling --- .../GetAllSubmodelElementsControllerTests.cs | 4 ++-- .../SubmodelRepository/Handler/SubmodelRepositoryHandler.cs | 2 +- .../Services/SubmodelRepository/SubmodelTemplateService.cs | 2 +- 3 files changed, 4 insertions(+), 4 deletions(-) diff --git a/source/AAS.TwinEngine.DataEngine.ModuleTests/Api/Services/SubmodelRepository/GetAllSubmodelElementsControllerTests.cs b/source/AAS.TwinEngine.DataEngine.ModuleTests/Api/Services/SubmodelRepository/GetAllSubmodelElementsControllerTests.cs index d7ddc9f4..0ac9ecd6 100644 --- a/source/AAS.TwinEngine.DataEngine.ModuleTests/Api/Services/SubmodelRepository/GetAllSubmodelElementsControllerTests.cs +++ b/source/AAS.TwinEngine.DataEngine.ModuleTests/Api/Services/SubmodelRepository/GetAllSubmodelElementsControllerTests.cs @@ -80,7 +80,7 @@ public async Task GetAllSubmodelElementsAsync_WithNoQueryParams_ReturnsOkWithEmp }; _ = _mockSubmodelRepositoryService - .GetAllSubmodelElementsAsync(SubmodelId, null, null, null, Arg.Any()) + .GetAllSubmodelElementsAsync(SubmodelId, Arg.Any(), null, null, Arg.Any()) .Returns(elementList); // Act @@ -105,7 +105,7 @@ public async Task GetAllSubmodelElementsAsync_WithElements_ReturnsPopulatedResul }; _ = _mockSubmodelRepositoryService - .GetAllSubmodelElementsAsync(SubmodelId, null, null, null, Arg.Any()) + .GetAllSubmodelElementsAsync(SubmodelId, Arg.Any(), null, null, Arg.Any()) .Returns(elementList); // Act diff --git a/source/AAS.TwinEngine.DataEngine/Api/SubmodelRepository/Handler/SubmodelRepositoryHandler.cs b/source/AAS.TwinEngine.DataEngine/Api/SubmodelRepository/Handler/SubmodelRepositoryHandler.cs index f007b72e..325fe820 100644 --- a/source/AAS.TwinEngine.DataEngine/Api/SubmodelRepository/Handler/SubmodelRepositoryHandler.cs +++ b/source/AAS.TwinEngine.DataEngine/Api/SubmodelRepository/Handler/SubmodelRepositoryHandler.cs @@ -37,7 +37,7 @@ public Task GetSubmodelElement(GetSubmodelElementRequest reque return GetResourceByIdAsync( request?.SubmodelId, "submodel element", - id => submodelRepositoryService.GetSubmodelElementAsync(id, decodedIdShortPath, queryOptions, cancellationToken)!); + id => submodelRepositoryService.GetSubmodelElementAsync(id, decodedIdShortPath, queryOptions, cancellationToken)); } public async Task GetAllSubmodels(GetAllSubmodelsRequest request, CancellationToken cancellationToken) diff --git a/source/AAS.TwinEngine.DataEngine/ApplicationLogic/Services/SubmodelRepository/SubmodelTemplateService.cs b/source/AAS.TwinEngine.DataEngine/ApplicationLogic/Services/SubmodelRepository/SubmodelTemplateService.cs index 7625ce2b..8f1ba0fa 100644 --- a/source/AAS.TwinEngine.DataEngine/ApplicationLogic/Services/SubmodelRepository/SubmodelTemplateService.cs +++ b/source/AAS.TwinEngine.DataEngine/ApplicationLogic/Services/SubmodelRepository/SubmodelTemplateService.cs @@ -59,7 +59,7 @@ public async Task GetSubmodelTemplateAsync(string submodelId, string ValidateSubmodelId(submodelId); var templateId = _submodelTemplateMappingProvider.GetTemplateId(submodelId); - var submodel = await _templateProvider.GetFilteredSubmodelTemplateAsync(templateId!, queryOptions, cancellationToken).ConfigureAwait(false); + var submodel = await _templateProvider.GetFilteredSubmodelTemplateAsync(templateId, queryOptions, cancellationToken).ConfigureAwait(false); return BuildSubmodel(submodel, idShortPath); } From be87f2ffa5d25ec0f6ae66620fa78164ebc62dd7 Mon Sep 17 00:00:00 2001 From: mm-omdi Date: Mon, 17 Aug 2026 12:19:50 +0530 Subject: [PATCH 22/25] Refactor request records and handlers to use consistent property casing and simplify object initialization --- .../Handler/SubmodelRepositoryHandler.cs | 14 ++++------- .../Requests/GetAllSubmodelElementsRequest.cs | 22 +--------------- .../Requests/GetAllSubmodelsRequest.cs | 15 +---------- .../Requests/GetSubmodelElementRequest.cs | 2 +- .../Requests/GetSubmodelRequest.cs | 2 +- .../SubmodelRepositoryController.cs | 25 ++++++------------- 6 files changed, 17 insertions(+), 63 deletions(-) diff --git a/source/AAS.TwinEngine.DataEngine/Api/SubmodelRepository/Handler/SubmodelRepositoryHandler.cs b/source/AAS.TwinEngine.DataEngine/Api/SubmodelRepository/Handler/SubmodelRepositoryHandler.cs index 325fe820..e3fb4f9a 100644 --- a/source/AAS.TwinEngine.DataEngine/Api/SubmodelRepository/Handler/SubmodelRepositoryHandler.cs +++ b/source/AAS.TwinEngine.DataEngine/Api/SubmodelRepository/Handler/SubmodelRepositoryHandler.cs @@ -17,10 +17,10 @@ public class SubmodelRepositoryHandler( { public Task GetSubmodel(GetSubmodelRequest request, CancellationToken cancellationToken) { - var queryOptions = request?.level is not null || request?.extent is not null ? new SubmodelQueryOptions(request.level?.ToString(), request.extent?.ToString()) : null; + var queryOptions = new SubmodelQueryOptions(request?.Level.ToString(), request?.Extent.ToString()); return GetResourceByIdAsync( - request?.submodelId, + request?.SubmodelId, "submodel", id => submodelRepositoryService.GetSubmodelAsync(id, queryOptions, cancellationToken)!); } @@ -30,9 +30,7 @@ public Task GetSubmodelElement(GetSubmodelElementRequest reque var decodedIdShortPath = Uri.UnescapeDataString(request?.IdShortPath ?? string.Empty); decodedIdShortPath.ValidateIdShortPath(nameof(request.IdShortPath), logger); - var queryOptions = request?.level is not null || request?.extent is not null - ? new SubmodelQueryOptions(request.level?.ToString(), request.extent?.ToString()) - : null; + var queryOptions = new SubmodelQueryOptions(request?.Level.ToString(), request?.Extent.ToString()); return GetResourceByIdAsync( request?.SubmodelId, @@ -51,7 +49,7 @@ public async Task GetAllSubmodels(GetAllSubmodelsRequest request, IdShort = request?.IdShort }; - var queryOptions = request?.Level is not null || request?.Extent is not null ? new SubmodelQueryOptions(request.Level?.ToString(), request.Extent?.ToString()) : null; + var queryOptions = new SubmodelQueryOptions(request?.Level.ToString(), request?.Extent.ToString()); var result = await submodelRepositoryService.GetAllSubmodelsAsync(filter, queryOptions, request?.Limit, request?.Cursor, cancellationToken).ConfigureAwait(false); @@ -63,9 +61,7 @@ public async Task GetAllSubmodelElements(GetAllSubmodelElem request?.Limit.ValidateLimit(logger); request?.Cursor?.ValidateCursor(logger); - var queryOptions = request?.Level is not null || request?.Extent is not null - ? new SubmodelQueryOptions(request.Level?.ToString(), request.Extent?.ToString()) - : null; + var queryOptions = new SubmodelQueryOptions(request?.Level.ToString(), request?.Extent.ToString()); var result = await GetResourceByIdAsync( request?.SubmodelId, diff --git a/source/AAS.TwinEngine.DataEngine/Api/SubmodelRepository/Requests/GetAllSubmodelElementsRequest.cs b/source/AAS.TwinEngine.DataEngine/Api/SubmodelRepository/Requests/GetAllSubmodelElementsRequest.cs index c2d8397d..6a490961 100644 --- a/source/AAS.TwinEngine.DataEngine/Api/SubmodelRepository/Requests/GetAllSubmodelElementsRequest.cs +++ b/source/AAS.TwinEngine.DataEngine/Api/SubmodelRepository/Requests/GetAllSubmodelElementsRequest.cs @@ -1,23 +1,3 @@ namespace AAS.TwinEngine.DataEngine.Api.SubmodelRepository.Requests; -public record GetAllSubmodelElementsRequest -{ - public string? SubmodelId { get; init; } - - public int? Limit { get; init; } - - public string? Cursor { get; init; } - - public Level? Level { get; init; } - - public Extent? Extent { get; init; } - - public GetAllSubmodelElementsRequest(string? submodelId, int? limit, string? cursor, Level? level = null, Extent? extent = null) - { - SubmodelId = submodelId; - Limit = limit; - Cursor = cursor; - Level = level; - Extent = extent; - } -} +public record GetAllSubmodelElementsRequest(string? SubmodelId, int? Limit, string? Cursor, Level Level, Extent Extent); diff --git a/source/AAS.TwinEngine.DataEngine/Api/SubmodelRepository/Requests/GetAllSubmodelsRequest.cs b/source/AAS.TwinEngine.DataEngine/Api/SubmodelRepository/Requests/GetAllSubmodelsRequest.cs index 1aae2ddb..10d9d390 100644 --- a/source/AAS.TwinEngine.DataEngine/Api/SubmodelRepository/Requests/GetAllSubmodelsRequest.cs +++ b/source/AAS.TwinEngine.DataEngine/Api/SubmodelRepository/Requests/GetAllSubmodelsRequest.cs @@ -1,19 +1,6 @@ namespace AAS.TwinEngine.DataEngine.Api.SubmodelRepository.Requests; -public record GetAllSubmodelsRequest -{ - public string? SemanticId { get; set; } - - public string? IdShort { get; set; } - - public int? Limit { get; set; } - - public string? Cursor { get; set; } - - public Level? Level { get; set; } - - public Extent? Extent { get; set; } -} +public record GetAllSubmodelsRequest(string? SemanticId, string? IdShort, int? Limit, string? Cursor, Level Level, Extent Extent); public enum Level { diff --git a/source/AAS.TwinEngine.DataEngine/Api/SubmodelRepository/Requests/GetSubmodelElementRequest.cs b/source/AAS.TwinEngine.DataEngine/Api/SubmodelRepository/Requests/GetSubmodelElementRequest.cs index b170eafb..194806ce 100644 --- a/source/AAS.TwinEngine.DataEngine/Api/SubmodelRepository/Requests/GetSubmodelElementRequest.cs +++ b/source/AAS.TwinEngine.DataEngine/Api/SubmodelRepository/Requests/GetSubmodelElementRequest.cs @@ -1,3 +1,3 @@ namespace AAS.TwinEngine.DataEngine.Api.SubmodelRepository.Requests; -public record GetSubmodelElementRequest(string SubmodelId, string IdShortPath, Level? level = Level.deep, Extent? extent = Extent.withoutBlobValue); \ No newline at end of file +public record GetSubmodelElementRequest(string SubmodelId, string IdShortPath, Level? Level = Level.deep, Extent? Extent = Extent.withoutBlobValue); \ No newline at end of file diff --git a/source/AAS.TwinEngine.DataEngine/Api/SubmodelRepository/Requests/GetSubmodelRequest.cs b/source/AAS.TwinEngine.DataEngine/Api/SubmodelRepository/Requests/GetSubmodelRequest.cs index 1ce5ad27..eca1d23f 100644 --- a/source/AAS.TwinEngine.DataEngine/Api/SubmodelRepository/Requests/GetSubmodelRequest.cs +++ b/source/AAS.TwinEngine.DataEngine/Api/SubmodelRepository/Requests/GetSubmodelRequest.cs @@ -1,3 +1,3 @@ namespace AAS.TwinEngine.DataEngine.Api.SubmodelRepository.Requests; -public record GetSubmodelRequest(string submodelId, Level? level = Level.deep, Extent? extent = Extent.withoutBlobValue); +public record GetSubmodelRequest(string SubmodelId, Level? Level = Level.deep, Extent? Extent = Extent.withoutBlobValue); diff --git a/source/AAS.TwinEngine.DataEngine/Api/SubmodelRepository/SubmodelRepositoryController.cs b/source/AAS.TwinEngine.DataEngine/Api/SubmodelRepository/SubmodelRepositoryController.cs index a5aae4d4..f4d1fd1d 100644 --- a/source/AAS.TwinEngine.DataEngine/Api/SubmodelRepository/SubmodelRepositoryController.cs +++ b/source/AAS.TwinEngine.DataEngine/Api/SubmodelRepository/SubmodelRepositoryController.cs @@ -56,15 +56,7 @@ public async Task> GetAllSubmodelsAsync( { logger.LogInformation("Get All Submodels"); - var request = new GetAllSubmodelsRequest - { - SemanticId = semanticId, - IdShort = idShort, - Limit = limit, - Cursor = cursor, - Level = level, - Extent = extent - }; + var request = new GetAllSubmodelsRequest(semanticId, idShort, limit, cursor, level, extent); var response = await submodelRepositoryHandler .GetAllSubmodels(request, cancellationToken) @@ -90,9 +82,9 @@ public async Task> GetAllSubmodelsAsync( [ProducesResponseType(typeof(ServiceErrorResponse), (int)HttpStatusCode.InternalServerError)] public async Task> GetSubmodelAsync( [FromRoute] string submodelIdentifier, - [FromQuery] Level? level, - [FromQuery] Extent? extent, - CancellationToken cancellationToken) + CancellationToken cancellationToken, + [FromQuery] Level level = Level.deep, + [FromQuery] Extent extent = Extent.withoutBlobValue) { logger.LogInformation("Get Submodel"); var request = new GetSubmodelRequest(submodelIdentifier, level, extent); @@ -123,8 +115,7 @@ public async Task> GetAllSubmodelElementsAsync [FromQuery] string? cursor, CancellationToken cancellationToken, [FromQuery] Level level = Level.deep, - [FromQuery] Extent extent = Extent.withoutBlobValue - ) + [FromQuery] Extent extent = Extent.withoutBlobValue) { logger.LogInformation("Get All Submodel Elements"); var request = new GetAllSubmodelElementsRequest(submodelIdentifier, limit, cursor, level, extent); @@ -151,9 +142,9 @@ public async Task> GetAllSubmodelElementsAsync public async Task> GetSubmodelElementAsync( [FromRoute] string submodelIdentifier, [FromRoute] string idShortPath, - [FromQuery] Level? level, - [FromQuery] Extent? extent, - CancellationToken cancellationToken) + CancellationToken cancellationToken, + [FromQuery] Level level = Level.deep, + [FromQuery] Extent extent = Extent.withoutBlobValue) { logger.LogInformation("Get Submodel Element"); var request = new GetSubmodelElementRequest(submodelIdentifier, idShortPath, level, extent); From 87db2ffb5e83429dbf05641bcc0302ea8fc9681a Mon Sep 17 00:00:00 2001 From: mm-omdi Date: Mon, 17 Aug 2026 12:43:31 +0530 Subject: [PATCH 23/25] Refactor GetAllSubmodelsRequest to use property initialization and update related tests for consistency --- .../Handler/SubmodelRepositoryHandlerTests.cs | 16 +++++++------- .../SubmodelRepositoryControllerTests.cs | 22 +++++++++++-------- .../Requests/GetAllSubmodelsRequest.cs | 17 ++++++++++++-- .../SubmodelRepositoryController.cs | 2 +- 4 files changed, 37 insertions(+), 20 deletions(-) diff --git a/source/AAS.TwinEngine.DataEngine.UnitTests/Api/SubmodelRepository/Handler/SubmodelRepositoryHandlerTests.cs b/source/AAS.TwinEngine.DataEngine.UnitTests/Api/SubmodelRepository/Handler/SubmodelRepositoryHandlerTests.cs index 858113cb..6844fe72 100644 --- a/source/AAS.TwinEngine.DataEngine.UnitTests/Api/SubmodelRepository/Handler/SubmodelRepositoryHandlerTests.cs +++ b/source/AAS.TwinEngine.DataEngine.UnitTests/Api/SubmodelRepository/Handler/SubmodelRepositoryHandlerTests.cs @@ -323,7 +323,7 @@ public async Task GetAllSubmodels_ReturnsSubmodelsDto_WhenServiceSucceeds() [Fact] public async Task GetAllSubmodels_WithInvalidLimit_ThrowsInvalidUserInputException() { - var request = new GetAllSubmodelsRequest { Limit = 0 }; + var request = new GetAllSubmodelsRequest {Limit = 0 }; await Assert.ThrowsAsync(() => _sut.GetAllSubmodels(request, CancellationToken.None)); } @@ -391,7 +391,7 @@ public async Task GetAllSubmodelElements_DecodesSubmodelIdAndPassesToService() { const string SubmodelId = "NameplateSubmodel"; var encodedId = WebEncoders.Base64UrlEncode(Encoding.UTF8.GetBytes(SubmodelId)); - var request = new GetAllSubmodelElementsRequest(encodedId, null, null); + var request = new GetAllSubmodelElementsRequest(encodedId, null, null, Level.deep, Extent.withoutBlobValue); var elementList = new SubmodelElementsPage { PagingMetaData = new DomainModel.Shared.PagingMetaData(), Result = [] }; _submodelRepository @@ -412,7 +412,7 @@ public async Task GetAllSubmodelElements_PassesLimitAndCursorToService() const int Limit = 5; const string Cursor = "dGVzdA=="; var encodedId = WebEncoders.Base64UrlEncode(Encoding.UTF8.GetBytes(SubmodelId)); - var request = new GetAllSubmodelElementsRequest(encodedId, Limit, Cursor); + var request = new GetAllSubmodelElementsRequest(encodedId, Limit, Cursor, Level.deep, Extent.withoutBlobValue); var elementList = new SubmodelElementsPage { PagingMetaData = new DomainModel.Shared.PagingMetaData(), Result = [] }; _submodelRepository @@ -430,7 +430,7 @@ public async Task GetAllSubmodelElements_ReturnsDto_WithElementsAndPagingMetadat { const string SubmodelId = "NameplateSubmodel"; var encodedId = WebEncoders.Base64UrlEncode(Encoding.UTF8.GetBytes(SubmodelId)); - var request = new GetAllSubmodelElementsRequest(encodedId, null, null); + var request = new GetAllSubmodelElementsRequest(encodedId, null, null, Level.deep, Extent.withoutBlobValue); var element = Substitute.For(); element.IdShort.Returns("ManufacturerName"); var elementList = new SubmodelElementsPage @@ -468,12 +468,12 @@ await _submodelRepository.Received(1) } [Fact] - public async Task GetAllSubmodelElements_WithNoLevelOrExtent_PassesNullQueryOptionsToService() + public async Task GetAllSubmodelElements_WithNoLevelOrExtent_PassesDefaultQueryOptionsToService() { const string SubmodelId = "NameplateSubmodel"; var encodedId = WebEncoders.Base64UrlEncode(Encoding.UTF8.GetBytes(SubmodelId)); - var request = new GetAllSubmodelElementsRequest(encodedId, null, null); - var elementList = new SubmodelElementsPage { PagingMetaData = new DomainModel.Shared.PagingMetaData(), Result = [] }; + var request = new GetAllSubmodelElementsRequest(encodedId, null, null, Level.deep, Extent.withoutBlobValue); + var elementList = new SubmodelElementsPage { PagingMetaData = new PagingMetaData(), Result = [] }; _submodelRepository .GetAllSubmodelElementsAsync(SubmodelId, null, null, null, Arg.Any()) @@ -482,7 +482,7 @@ public async Task GetAllSubmodelElements_WithNoLevelOrExtent_PassesNullQueryOpti await _sut.GetAllSubmodelElements(request, CancellationToken.None); await _submodelRepository.Received(1) - .GetAllSubmodelElementsAsync(SubmodelId, null, null, null, Arg.Any()); + .GetAllSubmodelElementsAsync(SubmodelId, Arg.Is(q => q != null), null, null, Arg.Any()); } [Fact] diff --git a/source/AAS.TwinEngine.DataEngine.UnitTests/Api/SubmodelRepository/SubmodelRepositoryControllerTests.cs b/source/AAS.TwinEngine.DataEngine.UnitTests/Api/SubmodelRepository/SubmodelRepositoryControllerTests.cs index 6f84c64d..0f3152a3 100644 --- a/source/AAS.TwinEngine.DataEngine.UnitTests/Api/SubmodelRepository/SubmodelRepositoryControllerTests.cs +++ b/source/AAS.TwinEngine.DataEngine.UnitTests/Api/SubmodelRepository/SubmodelRepositoryControllerTests.cs @@ -63,7 +63,7 @@ public async Task GetSubmodelAsync_ReturnsOkResult_WithJsonObject() _handler.GetSubmodel(Arg.Any(), Arg.Any()) .Returns(_expectedSubmodel); - var result = await _sut.GetSubmodelAsync(encodedId, null, null, CancellationToken.None); + var result = await _sut.GetSubmodelAsync(encodedId, CancellationToken.None); var okResult = Assert.IsType(result.Result); var json = Assert.IsType(okResult.Value); @@ -77,12 +77,11 @@ public async Task GetSubmodelAsync_WithLevelAndExtent_PassesThemToHandler() _handler.GetSubmodel(Arg.Any(), Arg.Any()) .Returns(_expectedSubmodel); - await _sut.GetSubmodelAsync(encodedId, AAS.TwinEngine.DataEngine.Api.SubmodelRepository.Requests.Level.deep, - AAS.TwinEngine.DataEngine.Api.SubmodelRepository.Requests.Extent.withBlobValue, CancellationToken.None); + await _sut.GetSubmodelAsync(encodedId, CancellationToken.None, Level.deep, Extent.withBlobValue); await _handler.Received(1).GetSubmodel( - Arg.Is(r => r.level == AAS.TwinEngine.DataEngine.Api.SubmodelRepository.Requests.Level.deep - && r.extent == AAS.TwinEngine.DataEngine.Api.SubmodelRepository.Requests.Extent.withBlobValue), + Arg.Is(r => r.Level == Level.deep + && r.Extent == Extent.withBlobValue), Arg.Any()); } @@ -94,7 +93,7 @@ public async Task GetSubmodelElementAsync_ReturnsOkResult_WithJsonObject() _handler.GetSubmodelElement(Arg.Any(), Arg.Any()) .Returns(_expectedElement); - var result = await _sut.GetSubmodelElementAsync(encodedId, _idShortPath, null, null, CancellationToken.None); + var result = await _sut.GetSubmodelElementAsync(encodedId, _idShortPath, CancellationToken.None, Level.deep, Extent.withBlobValue); var okResult = Assert.IsType(result.Result); var json = Assert.IsType(okResult.Value); @@ -108,10 +107,10 @@ public async Task GetSubmodelElementAsync_WithLevelAndExtent_PassesThemToHandler _handler.GetSubmodelElement(Arg.Any(), Arg.Any()) .Returns(_expectedElement); - await _sut.GetSubmodelElementAsync(encodedId, _idShortPath, Level.deep, Extent.withBlobValue, CancellationToken.None); + await _sut.GetSubmodelElementAsync(encodedId, _idShortPath, CancellationToken.None, Level.deep, Extent.withBlobValue); await _handler.Received(1).GetSubmodelElement( - Arg.Is(r => r.level == Level.deep && r.extent == Extent.withBlobValue), + Arg.Is(r => r.Level == Level.deep && r.Extent == Extent.withBlobValue), Arg.Any()); } @@ -142,7 +141,12 @@ public async Task GetAllSubmodelsAsync_PassesQueryParamsToHandler() var expectedDto = new SubmodelsDto { PagingMetaData = new AAS.TwinEngine.DataEngine.Api.Shared.PagingMetaDataDto(), Result = [] }; _handler.GetAllSubmodels(Arg.Any(), Arg.Any()) .Returns(expectedDto); - var request = new GetAllSubmodelsRequest { SemanticId = SemanticId, IdShort = IdShort, Limit = Limit }; + var request = new GetAllSubmodelsRequest + { + SemanticId = SemanticId, + IdShort = IdShort, + Limit = Limit + }; await _sut.GetAllSubmodelsAsync(SemanticId, IdShort, Limit, null, CancellationToken.None); diff --git a/source/AAS.TwinEngine.DataEngine/Api/SubmodelRepository/Requests/GetAllSubmodelsRequest.cs b/source/AAS.TwinEngine.DataEngine/Api/SubmodelRepository/Requests/GetAllSubmodelsRequest.cs index 10d9d390..50f04779 100644 --- a/source/AAS.TwinEngine.DataEngine/Api/SubmodelRepository/Requests/GetAllSubmodelsRequest.cs +++ b/source/AAS.TwinEngine.DataEngine/Api/SubmodelRepository/Requests/GetAllSubmodelsRequest.cs @@ -1,6 +1,19 @@ namespace AAS.TwinEngine.DataEngine.Api.SubmodelRepository.Requests; -public record GetAllSubmodelsRequest(string? SemanticId, string? IdShort, int? Limit, string? Cursor, Level Level, Extent Extent); +public record GetAllSubmodelsRequest +{ + public string? SemanticId { get; set; } + + public string? IdShort { get; set; } + + public int? Limit { get; set; } + + public string? Cursor { get; set; } + + public Level? Level { get; set; } + + public Extent? Extent { get; set; } +} public enum Level { @@ -12,4 +25,4 @@ public enum Extent { withBlobValue, withoutBlobValue -} \ No newline at end of file +} diff --git a/source/AAS.TwinEngine.DataEngine/Api/SubmodelRepository/SubmodelRepositoryController.cs b/source/AAS.TwinEngine.DataEngine/Api/SubmodelRepository/SubmodelRepositoryController.cs index f4d1fd1d..e4490393 100644 --- a/source/AAS.TwinEngine.DataEngine/Api/SubmodelRepository/SubmodelRepositoryController.cs +++ b/source/AAS.TwinEngine.DataEngine/Api/SubmodelRepository/SubmodelRepositoryController.cs @@ -56,7 +56,7 @@ public async Task> GetAllSubmodelsAsync( { logger.LogInformation("Get All Submodels"); - var request = new GetAllSubmodelsRequest(semanticId, idShort, limit, cursor, level, extent); + var request = new GetAllSubmodelsRequest{SemanticId = semanticId, IdShort = idShort, Limit = limit, Cursor = cursor, Level = level, Extent = extent}; var response = await submodelRepositoryHandler .GetAllSubmodels(request, cancellationToken) From 8fc8c4f604615954a9b6c49df6315a2afdeb6284 Mon Sep 17 00:00:00 2001 From: mm-omdi Date: Mon, 17 Aug 2026 12:50:57 +0530 Subject: [PATCH 24/25] Refactor GetAllSubmodels and GetAllSubmodelElements tests to improve query options handling and ensure consistent formatting --- .../Handler/SubmodelRepositoryHandlerTests.cs | 20 ++++++++++++++++--- 1 file changed, 17 insertions(+), 3 deletions(-) diff --git a/source/AAS.TwinEngine.DataEngine.UnitTests/Api/SubmodelRepository/Handler/SubmodelRepositoryHandlerTests.cs b/source/AAS.TwinEngine.DataEngine.UnitTests/Api/SubmodelRepository/Handler/SubmodelRepositoryHandlerTests.cs index 6844fe72..83b2b68b 100644 --- a/source/AAS.TwinEngine.DataEngine.UnitTests/Api/SubmodelRepository/Handler/SubmodelRepositoryHandlerTests.cs +++ b/source/AAS.TwinEngine.DataEngine.UnitTests/Api/SubmodelRepository/Handler/SubmodelRepositoryHandlerTests.cs @@ -323,7 +323,7 @@ public async Task GetAllSubmodels_ReturnsSubmodelsDto_WhenServiceSucceeds() [Fact] public async Task GetAllSubmodels_WithInvalidLimit_ThrowsInvalidUserInputException() { - var request = new GetAllSubmodelsRequest {Limit = 0 }; + var request = new GetAllSubmodelsRequest { Limit = 0 }; await Assert.ThrowsAsync(() => _sut.GetAllSubmodels(request, CancellationToken.None)); } @@ -476,13 +476,27 @@ public async Task GetAllSubmodelElements_WithNoLevelOrExtent_PassesDefaultQueryO var elementList = new SubmodelElementsPage { PagingMetaData = new PagingMetaData(), Result = [] }; _submodelRepository - .GetAllSubmodelElementsAsync(SubmodelId, null, null, null, Arg.Any()) + .GetAllSubmodelElementsAsync( + SubmodelId, + Arg.Is(q => q != null + && q.Level == Level.deep.ToString() + && q.Extent == Extent.withoutBlobValue.ToString()), + null, + null, + Arg.Any()) .Returns(elementList); await _sut.GetAllSubmodelElements(request, CancellationToken.None); await _submodelRepository.Received(1) - .GetAllSubmodelElementsAsync(SubmodelId, Arg.Is(q => q != null), null, null, Arg.Any()); + .GetAllSubmodelElementsAsync( + SubmodelId, + Arg.Is(q => q != null + && q.Level == Level.deep.ToString() + && q.Extent == Extent.withoutBlobValue.ToString()), + null, + null, + Arg.Any()); } [Fact] From c89cb27d7345f9dc216134e5df60d0f4ca72f92f Mon Sep 17 00:00:00 2001 From: mm-omdi Date: Mon, 17 Aug 2026 13:15:59 +0530 Subject: [PATCH 25/25] Update GetFilteredSubmodelTemplateAsync calls to use SubmodelQueryOptions for improved parameter handling --- .../SubmodelRepository/SubmodelRepositoryControllerTests.cs | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/source/AAS.TwinEngine.DataEngine.ModuleTests/Api/Services/SubmodelRepository/SubmodelRepositoryControllerTests.cs b/source/AAS.TwinEngine.DataEngine.ModuleTests/Api/Services/SubmodelRepository/SubmodelRepositoryControllerTests.cs index 3ca82693..ba6e931d 100644 --- a/source/AAS.TwinEngine.DataEngine.ModuleTests/Api/Services/SubmodelRepository/SubmodelRepositoryControllerTests.cs +++ b/source/AAS.TwinEngine.DataEngine.ModuleTests/Api/Services/SubmodelRepository/SubmodelRepositoryControllerTests.cs @@ -89,7 +89,7 @@ public async Task GetSubmodelAsync_WithValidIdentifier_ReturnsOkAsync() const string SubmodelId = "Q29udGFjdEluZm9ybWF0aW9u"; var mockSubmodel = TestData.CreateSubmodel(); - _ = _mockTemplateProvider.GetFilteredSubmodelTemplateAsync(Arg.Any(), null, Arg.Any()).Returns(mockSubmodel); + _ = _mockTemplateProvider.GetFilteredSubmodelTemplateAsync(Arg.Any(), Arg.Any(), Arg.Any()).Returns(mockSubmodel); // Act var response = await _client.GetAsync($"/submodels/{SubmodelId}"); @@ -108,7 +108,7 @@ public async Task GetSubmodelAsync_WithNotFound_Returns404Async() { const string SubmodelId = "Q29udGFjdEluZm9ybWF0aW9u"; - _ = _mockTemplateProvider.GetFilteredSubmodelTemplateAsync(Arg.Any(), null, Arg.Any()).Throws(new ResourceNotFoundException()); + _ = _mockTemplateProvider.GetFilteredSubmodelTemplateAsync(Arg.Any(), Arg.Any(), Arg.Any()).Throws(new ResourceNotFoundException()); var response = await _client.GetAsync($"/submodels/{SubmodelId}"); @@ -177,7 +177,7 @@ public async Task GetSubmodelElementAsync_WithNotFound_Returns404Async() { const string SubmodelId = "Q29udGFjdEluZm9ybWF0aW9u"; - _ = _mockTemplateProvider.GetFilteredSubmodelTemplateAsync(Arg.Any(), null, Arg.Any()).Throws(new ResourceNotFoundException()); + _ = _mockTemplateProvider.GetFilteredSubmodelTemplateAsync(Arg.Any(), Arg.Any(), Arg.Any()).Throws(new ResourceNotFoundException()); var response = await _client.GetAsync(CreateSubmodelElementPath(SubmodelId, "Test"));