From 6cfc8a260d7ae03bbfe66ecfd294a6d2b4ef5d5c Mon Sep 17 00:00:00 2001 From: "Krzysztof Massalski (Extern)" Date: Thu, 7 Mar 2024 15:53:02 +0100 Subject: [PATCH] feat(impl):[#439] change impl of accepted aspects --- .config/checkstyle.xml | 4 +- docs/src/api/irs-api.yaml | 21 ++++++-- .../cross-cutting/json-response-model.puml | 2 +- .../irs/configuration/OpenApiExamples.java | 2 +- .../irs/semanticshub/SemanticsHubClient.java | 6 +-- .../services/IrsItemGraphQueryService.java | 6 +-- .../irs/services/MeterRegistryService.java | 2 +- .../tractusx/irs/IrsFunctionalTest.java | 23 ++++---- .../irs/IrsWireMockIntegrationTest.java | 5 +- .../eclipse/tractusx/irs/WiremockSupport.java | 4 +- .../delegate/RelationshipDelegateTest.java | 6 +-- .../job/delegate/SubmodelDelegateTest.java | 27 +++++----- ...rsItemGraphQueryServiceSpringBootTest.java | 53 ++++++------------- .../eclipse/tractusx/irs/util/TestMother.java | 29 +++++----- .../semantichub/all-models-page-IT.json | 7 +++ irs-common/pom.xml | 4 -- irs-models/pom.xml | 5 ++ .../irs/component/RegisterBatchOrder.java | 8 ++- .../RegisterBpnInvestigationJob.java | 2 +- .../tractusx/irs/component/RegisterJob.java | 8 +-- .../AssetAdministrationShellDescriptor.java | 37 +++---------- .../SemanticModel.java | 51 ++++++++++++++++++ .../SubmodelDescriptor.java | 26 ++++++++- ...setAdministrationShellTestdataCreator.java | 22 ++++---- ...dministrationShellTestdataCreatorTest.java | 4 +- ...CentralDigitalTwinRegistryServiceTest.java | 4 +- 26 files changed, 211 insertions(+), 157 deletions(-) create mode 100644 irs-models/src/main/java/org/eclipse/tractusx/irs/component/assetadministrationshell/SemanticModel.java diff --git a/.config/checkstyle.xml b/.config/checkstyle.xml index 36317a0e73..4770219683 100644 --- a/.config/checkstyle.xml +++ b/.config/checkstyle.xml @@ -249,7 +249,9 @@ page at http://checkstyle.sourceforge.net/config.html --> - + + + diff --git a/docs/src/api/irs-api.yaml b/docs/src/api/irs-api.yaml index 3d360986dd..53c740d030 100644 --- a/docs/src/api/irs-api.yaml +++ b/docs/src/api/irs-api.yaml @@ -3,7 +3,7 @@ info: description: The API of the Item Relationship Service (IRS) for retrieving item graphs along the value chain of CATENA-X partners. title: IRS API - version: "4.7.0" + version: 4.7.0 servers: - url: http://localhost:8080 security: @@ -1127,7 +1127,7 @@ components: semanticId: keys: - type: Submodel - value: urn:bamm:com.catenax.vehicle:0.1.1#PartDetails + value: urn:bamm:io.catenax.vehicle:0.1.1#PartDetails type: ModelReference submodels: - aspectType: supply_chain_impacted @@ -1250,7 +1250,7 @@ components: semanticId: keys: - type: Submodel - value: urn:bamm:com.catenax.vehicle:0.1.1#PartDetails + value: urn:bamm:io.catenax.vehicle:0.1.1#PartDetails type: ModelReference submodels: - aspectType: urn:bamm:io.catenax.single_level_bom_as_built:1.0.0 @@ -1441,7 +1441,7 @@ components: type: ModelReference keys: - type: Submodel - value: urn:bamm:com.catenax.vehicle:0.1.1#PartDetails + value: urn:bamm:io.catenax.vehicle:0.1.1#PartDetails submodels: - aspectType: urn:bamm:io.catenax.single_level_bom_as_built:1.0.0 contractAgreementId: f253718e-a270-4367-901b-9d50d9bd8462 @@ -2367,9 +2367,14 @@ components: properties: aspects: type: array + description: List of aspect names that will be collected if \ + flag is set to true. + example: urn:samm:io.catenax.single_level_bom_as_built:2.0.0#SingleLevelBomAsBuilt items: type: string + pattern: ^(urn:(b|s)amm:.*\d\.\d\.\d)?(#)?(\w+)?$ maxItems: 2147483647 + pattern: ^(urn:(b|s)amm:.*\d\.\d\.\d)?(#)?(\w+)?$ batchSize: type: integer format: int32 @@ -2511,7 +2516,6 @@ components: description: The requested job definition. properties: bomLifecycle: - example: asPlanned type: string description: The lifecycle context in which the child part was assembled into the parent part. @@ -2519,6 +2523,7 @@ components: - asBuilt - asPlanned - asSpecified + example: asPlanned callbackUrl: type: string description: "Callback url to notify requestor when job processing is finished.\ @@ -2529,10 +2534,13 @@ components: $ref: '#/components/schemas/PartChainIdentificationKey' incidentBPNSs: type: array + description: Array of BPNS numbers. + example: BPNS000000000DDD items: type: string pattern: "(BPN)[LSA][\\w\\d]{10}[\\w\\d]{2}" maxItems: 2147483647 + pattern: "(BPN)[LSA][\\w\\d]{10}[\\w\\d]{2}" required: - incidentBPNSs - key @@ -2545,9 +2553,12 @@ components: type: array description: List of aspect names that will be collected if \ flag is set to true. + example: urn:samm:io.catenax.single_level_bom_as_built:2.0.0#SingleLevelBomAsBuilt items: type: string + pattern: ^(urn:(b|s)amm:.*\d\.\d\.\d)?(#)?(\w+)?$ maxItems: 2147483647 + pattern: ^(urn:(b|s)amm:.*\d\.\d\.\d)?(#)?(\w+)?$ auditContractNegotiation: type: boolean description: Flag enables and disables auditing, including provisioning diff --git a/docs/src/uml-diagrams/cross-cutting/json-response-model.puml b/docs/src/uml-diagrams/cross-cutting/json-response-model.puml index c3978b9bfa..dda48e443b 100644 --- a/docs/src/uml-diagrams/cross-cutting/json-response-model.puml +++ b/docs/src/uml-diagrams/cross-cutting/json-response-model.puml @@ -108,7 +108,7 @@ skinparam shadowing false "identification": "dae4d249-6d66-4818-b576-bf52f3b9ae90", "semanticId": { "value": [ - "urn:bamm:com.catenax.vehicle:0.1.1#PartDetails" + "urn:bamm:io.catenax.vehicle:0.1.1#PartDetails" ] }, "endpoints": [ diff --git a/irs-api/src/main/java/org/eclipse/tractusx/irs/configuration/OpenApiExamples.java b/irs-api/src/main/java/org/eclipse/tractusx/irs/configuration/OpenApiExamples.java index 2e04d95ba3..79eff9eca8 100644 --- a/irs-api/src/main/java/org/eclipse/tractusx/irs/configuration/OpenApiExamples.java +++ b/irs-api/src/main/java/org/eclipse/tractusx/irs/configuration/OpenApiExamples.java @@ -468,7 +468,7 @@ private SubmodelDescriptor createPartSubmodelDescriptor() { .semanticId(Reference.builder() .keys(List.of(SemanticId.builder() .type("Submodel") - .value("urn:bamm:com.catenax.vehicle:0.1.1#PartDetails") + .value("urn:bamm:io.catenax.vehicle:0.1.1#PartDetails") .build())) .type("ModelReference") .build()) diff --git a/irs-api/src/main/java/org/eclipse/tractusx/irs/semanticshub/SemanticsHubClient.java b/irs-api/src/main/java/org/eclipse/tractusx/irs/semanticshub/SemanticsHubClient.java index 9bfb528a7a..c284e3a68e 100644 --- a/irs-api/src/main/java/org/eclipse/tractusx/irs/semanticshub/SemanticsHubClient.java +++ b/irs-api/src/main/java/org/eclipse/tractusx/irs/semanticshub/SemanticsHubClient.java @@ -92,10 +92,10 @@ public List getAllAspectModels() { return List.of( new AspectModel("urn:bamm:io.catenax.serial_part:1.0.1#SerialPart", "1.0.1", "SerialPart", MODEL_TYPE, MODEL_STATUS), - new AspectModel("urn:bamm:com.catenax.esr_certificates.esr_certificate:1.0.0#EsrCertificate", "1.0.0", + new AspectModel("urn:bamm:io.catenax.esr_certificates.esr_certificate:1.0.0#EsrCertificate", "1.0.0", "EsrCertificate", MODEL_TYPE, MODEL_STATUS), - new AspectModel("urn:bamm:io.catenax.single_level_bom_as_built:1.0.0#SingleLevelBomAsBuilt", - "1.0.0", "SingleLevelBomAsBuilt", MODEL_TYPE, MODEL_STATUS), + new AspectModel("urn:bamm:io.catenax.single_level_bom_as_built:2.0.0#SingleLevelBomAsBuilt", + "2.0.0", "SingleLevelBomAsBuilt", MODEL_TYPE, MODEL_STATUS), new AspectModel("urn:bamm:io.catenax.part_as_specified:2.0.0#PartAsSpecified", "2.0.0", "PartAsSpecified", MODEL_TYPE, MODEL_STATUS), new AspectModel("urn:bamm:io.catenax.part_as_planned:1.0.1#PartAsPlanned", diff --git a/irs-api/src/main/java/org/eclipse/tractusx/irs/services/IrsItemGraphQueryService.java b/irs-api/src/main/java/org/eclipse/tractusx/irs/services/IrsItemGraphQueryService.java index c8510d6849..197193c0c8 100644 --- a/irs-api/src/main/java/org/eclipse/tractusx/irs/services/IrsItemGraphQueryService.java +++ b/irs-api/src/main/java/org/eclipse/tractusx/irs/services/IrsItemGraphQueryService.java @@ -198,14 +198,12 @@ private void validateAspectTypeValues(final List aspectTypeValues) { semanticsHubFacade.getAllAspectModels().models()); log.debug("Number of available AspectModels: '{}'", availableModels.size()); log.debug("Provided AspectModels: '{}'", aspectTypeValues); - final Set availableNames = new HashSet<>(availableModels.stream().map(AspectModel::name).toList()); final Set availableUrns = new HashSet<>(availableModels.stream().map(AspectModel::urn).toList()); final List invalidAspectTypes = aspectTypeValues.stream() .filter(s -> !availableUrns.contains(s) - && !availableNames.contains(s) || !s.matches( - "^(urn:bamm:.*\\d\\.\\d\\.\\d)?(#)?(\\w+)?$")) + "^(urn:(b|s)amm:.*\\d\\.\\d\\.\\d)?(#)?(\\w+)?$")) .toList(); if (!invalidAspectTypes.isEmpty()) { throw new IllegalArgumentException( @@ -287,7 +285,7 @@ public Jobs getJobForJobId(final MultiTransferJob multiJob, final boolean includ public void updateJobsInJobStoreMetrics() { final List jobs = jobStore.findAll(); final long numberOfJobs = jobs.size(); - log.debug("Number(s) of job in JobStore: {}", numberOfJobs); + log.trace("Number(s) of job in JobStore: {}", numberOfJobs); meterRegistryService.setNumberOfJobsInJobStore(numberOfJobs); final Map stateCount = jobs.stream() diff --git a/irs-api/src/main/java/org/eclipse/tractusx/irs/services/MeterRegistryService.java b/irs-api/src/main/java/org/eclipse/tractusx/irs/services/MeterRegistryService.java index 8b5cd0b963..b53c39e590 100644 --- a/irs-api/src/main/java/org/eclipse/tractusx/irs/services/MeterRegistryService.java +++ b/irs-api/src/main/java/org/eclipse/tractusx/irs/services/MeterRegistryService.java @@ -169,7 +169,7 @@ public void recordJobStateMetric(final JobState state) { public void setNumberOfJobsInJobStore(final Long size) { this.numbersOfJobsInJobStore.set(size); - log.debug("Current size of Job in JobStore is {}", size); + log.trace("Current size of Job in JobStore is {}", size); } public void setMeasuredMethodExecutionTime(final String tag, final long duration) { diff --git a/irs-api/src/test/java/org/eclipse/tractusx/irs/IrsFunctionalTest.java b/irs-api/src/test/java/org/eclipse/tractusx/irs/IrsFunctionalTest.java index 5595090752..04d6bddfc3 100644 --- a/irs-api/src/test/java/org/eclipse/tractusx/irs/IrsFunctionalTest.java +++ b/irs-api/src/test/java/org/eclipse/tractusx/irs/IrsFunctionalTest.java @@ -40,6 +40,8 @@ import org.eclipse.tractusx.irs.component.Jobs; import org.eclipse.tractusx.irs.component.RegisterJob; import org.eclipse.tractusx.irs.component.enums.JobState; +import org.eclipse.tractusx.irs.configuration.security.ApiKeyAuthentication; +import org.eclipse.tractusx.irs.configuration.security.ApiKeyAuthority; import org.eclipse.tractusx.irs.controllers.IrsController; import org.eclipse.tractusx.irs.registryclient.discovery.ConnectorEndpointsService; import org.eclipse.tractusx.irs.testing.containers.MinioContainer; @@ -58,7 +60,6 @@ import org.springframework.security.core.context.SecurityContext; import org.springframework.security.core.context.SecurityContextHolder; import org.springframework.security.oauth2.jwt.Jwt; -import org.springframework.security.oauth2.server.resource.authentication.JwtAuthenticationToken; import org.springframework.test.context.ActiveProfiles; import org.springframework.test.context.ContextConfiguration; import org.springframework.test.context.support.TestPropertySourceUtils; @@ -100,7 +101,7 @@ void shouldStartJobAndRetrieveResult() { when(connectorEndpointsService.fetchConnectorEndpoints(any())).thenReturn( List.of("http://localhost/discovery")); - thereIsJwtAuthentication(); + thereIsAuthentication(); final JobHandle jobHandle = controller.registerJobForGlobalAssetId(registerJob); final Optional finishedJob = Awaitility.await() @@ -130,7 +131,7 @@ void shouldFillSummaryWithoutBPNLookup() { when(connectorEndpointsService.fetchConnectorEndpoints(any())).thenReturn( List.of("http://localhost/discovery")); - thereIsJwtAuthentication(); + thereIsAuthentication(); final JobHandle jobHandle = controller.registerJobForGlobalAssetId(registerJob); final Optional finishedJob = Awaitility.await() @@ -156,7 +157,7 @@ void shouldFillSummaryWithBPNLookup() { final RegisterJob registerJob = TestMother.registerJobWithLookupBPNs(); when(connectorEndpointsService.fetchConnectorEndpoints(any())).thenReturn( List.of("http://localhost/discovery")); - thereIsJwtAuthentication(); + thereIsAuthentication(); final JobHandle jobHandle = controller.registerJobForGlobalAssetId(registerJob); final Optional finishedJob = Awaitility.await() @@ -177,28 +178,22 @@ void shouldFillSummaryWithBPNLookup() { assertThat(finishedJob.get().getJob().getSummary().getBpnLookups().getFailed()).isZero(); } - private void thereIsJwtAuthentication() { - final JwtAuthenticationToken jwtAuthenticationToken = new JwtAuthenticationToken(jwt(), - List.of(new SimpleGrantedAuthority(IrsRoles.VIEW_IRS))); + private void thereIsAuthentication() { + final ApiKeyAuthentication jwtAuthenticationToken = new ApiKeyAuthentication(new + ApiKeyAuthority("apiKey", List.of(new SimpleGrantedAuthority(IrsRoles.VIEW_IRS)))); jwtAuthenticationToken.setAuthenticated(true); SecurityContext securityContext = Mockito.mock(SecurityContext.class); when(securityContext.getAuthentication()).thenReturn(jwtAuthenticationToken); SecurityContextHolder.setContext(securityContext); } - Jwt jwt() { - return new Jwt("token", Instant.now(), Instant.now().plusSeconds(30), Map.of("alg", "none"), - Map.of(SUB, "sub", "clientId", "clientId", "bpn", "BPNL00000001CRHK")); - } - @NotNull private Callable> getJobDetails(final JobHandle jobHandle) { return () -> { try { - thereIsJwtAuthentication(); + thereIsAuthentication(); return Optional.ofNullable(controller.getJobById(jobHandle.getId(), true).getBody()); } catch (Exception e) { - e.printStackTrace(); return Optional.empty(); } }; diff --git a/irs-api/src/test/java/org/eclipse/tractusx/irs/IrsWireMockIntegrationTest.java b/irs-api/src/test/java/org/eclipse/tractusx/irs/IrsWireMockIntegrationTest.java index 2d9a98bdf6..185d74a8b4 100644 --- a/irs-api/src/test/java/org/eclipse/tractusx/irs/IrsWireMockIntegrationTest.java +++ b/irs-api/src/test/java/org/eclipse/tractusx/irs/IrsWireMockIntegrationTest.java @@ -44,6 +44,7 @@ import static org.eclipse.tractusx.irs.testing.wiremock.SubmodelFacadeWiremockSupport.PATH_NEGOTIATE; import static org.eclipse.tractusx.irs.testing.wiremock.SubmodelFacadeWiremockSupport.PATH_STATE; import static org.eclipse.tractusx.irs.testing.wiremock.SubmodelFacadeWiremockSupport.PATH_TRANSFER; +import static org.eclipse.tractusx.irs.util.TestMother.singleLevelBomAsBuiltAspectName; import java.time.Duration; import java.util.List; @@ -150,7 +151,7 @@ void shouldStartApplicationAndCollectSemanticModels() throws SchemaNotFoundExcep final AspectModels allAspectModels = semanticHubService.getAllAspectModels(); // Assert - assertThat(allAspectModels.models()).hasSize(78); + assertThat(allAspectModels.models()).hasSize(79); } @Test @@ -288,7 +289,7 @@ private void successfulRegistryAndDataRequest(final String globalAssetId, final "urn:samm:io.catenax.batch:2.0.0#Batch", batchFileName); final String singleLevelBomAsBuilt = WiremockSupport.submodelRequest(edcAssetId, "SingleLevelBomAsBuilt", - "urn:bamm:io.catenax.single_level_bom_as_built:2.0.0#SingleLevelBomAsBuilt", sbomFileName); + singleLevelBomAsBuiltAspectName, sbomFileName); final List submodelDescriptors = List.of(batch, singleLevelBomAsBuilt); diff --git a/irs-api/src/test/java/org/eclipse/tractusx/irs/WiremockSupport.java b/irs-api/src/test/java/org/eclipse/tractusx/irs/WiremockSupport.java index 9698272e7c..5146e44de5 100644 --- a/irs-api/src/test/java/org/eclipse/tractusx/irs/WiremockSupport.java +++ b/irs-api/src/test/java/org/eclipse/tractusx/irs/WiremockSupport.java @@ -31,6 +31,8 @@ import static org.eclipse.tractusx.irs.testing.wiremock.DtrWiremockSupport.DATAPLANE_PUBLIC_URL; import static org.eclipse.tractusx.irs.testing.wiremock.DtrWiremockSupport.submodelDescriptor; import static org.eclipse.tractusx.irs.testing.wiremock.WireMockConfig.responseWithStatus; +import static org.eclipse.tractusx.irs.util.TestMother.batchAspectName; +import static org.eclipse.tractusx.irs.util.TestMother.singleLevelBomAsBuiltAspectName; import java.nio.charset.StandardCharsets; import java.util.Base64; @@ -79,7 +81,7 @@ static RegisterJob jobRequest(final String globalAssetId, final String bpn, fina return RegisterJob.builder() .key(PartChainIdentificationKey.builder().bpn(bpn).globalAssetId(globalAssetId).build()) .depth(depth) - .aspects(List.of("Batch", "SingleLevelBomAsBuilt")) + .aspects(List.of(batchAspectName, singleLevelBomAsBuiltAspectName)) .collectAspects(true) .lookupBPNs(true) .direction(Direction.DOWNWARD) diff --git a/irs-api/src/test/java/org/eclipse/tractusx/irs/aaswrapper/job/delegate/RelationshipDelegateTest.java b/irs-api/src/test/java/org/eclipse/tractusx/irs/aaswrapper/job/delegate/RelationshipDelegateTest.java index 7d446883d4..62159f16b2 100644 --- a/irs-api/src/test/java/org/eclipse/tractusx/irs/aaswrapper/job/delegate/RelationshipDelegateTest.java +++ b/irs-api/src/test/java/org/eclipse/tractusx/irs/aaswrapper/job/delegate/RelationshipDelegateTest.java @@ -25,10 +25,11 @@ import static org.assertj.core.api.Assertions.assertThat; import static org.eclipse.tractusx.irs.util.TestMother.jobParameter; -import static org.eclipse.tractusx.irs.util.TestMother.jobParameterCollectAspects; import static org.eclipse.tractusx.irs.util.TestMother.jobParameterUpward; import static org.eclipse.tractusx.irs.util.TestMother.shell; import static org.eclipse.tractusx.irs.util.TestMother.shellDescriptor; +import static org.eclipse.tractusx.irs.util.TestMother.singleLevelBomAsBuiltAspectName; +import static org.eclipse.tractusx.irs.util.TestMother.singleLevelUsageAsBuiltAspectName; import static org.eclipse.tractusx.irs.util.TestMother.submodelDescriptorWithDspEndpoint; import static org.mockito.ArgumentMatchers.any; import static org.mockito.ArgumentMatchers.anyString; @@ -62,9 +63,6 @@ class RelationshipDelegateTest { final RelationshipDelegate relationshipDelegate = new RelationshipDelegate(null, submodelFacade, connectorEndpointsService, jsonUtil); - final String singleLevelBomAsBuiltAspectName = "urn:bamm:io.catenax.single_level_bom_as_built:2.0.0#SingleLevelBomAsBuilt"; - final String singleLevelUsageAsBuiltAspectName = "urn:bamm:io.catenax.single_level_usage_as_built:2.0.0#SingleLevelUsageAsBuilt"; - @Test void shouldFillItemContainerWithRelationshipAndAddChildIdsToProcess() throws EdcClientException, URISyntaxException, IOException { diff --git a/irs-api/src/test/java/org/eclipse/tractusx/irs/aaswrapper/job/delegate/SubmodelDelegateTest.java b/irs-api/src/test/java/org/eclipse/tractusx/irs/aaswrapper/job/delegate/SubmodelDelegateTest.java index e80ecdf3b1..efba84d9a8 100644 --- a/irs-api/src/test/java/org/eclipse/tractusx/irs/aaswrapper/job/delegate/SubmodelDelegateTest.java +++ b/irs-api/src/test/java/org/eclipse/tractusx/irs/aaswrapper/job/delegate/SubmodelDelegateTest.java @@ -26,8 +26,10 @@ import static org.assertj.core.api.Assertions.assertThat; import static org.eclipse.tractusx.irs.util.TestMother.jobParameterCollectAspects; import static org.eclipse.tractusx.irs.util.TestMother.jobParameterFilter; +import static org.eclipse.tractusx.irs.util.TestMother.serialPartAspectName; import static org.eclipse.tractusx.irs.util.TestMother.shell; import static org.eclipse.tractusx.irs.util.TestMother.shellDescriptor; +import static org.eclipse.tractusx.irs.util.TestMother.singleLevelBomAsBuiltAspectName; import static org.eclipse.tractusx.irs.util.TestMother.submodelDescriptor; import static org.eclipse.tractusx.irs.util.TestMother.submodelDescriptorWithDspEndpoint; import static org.mockito.ArgumentMatchers.any; @@ -71,10 +73,10 @@ void shouldFilterSubmodelDescriptorsByAspectTypeFilter() { final ItemContainer.ItemContainerBuilder itemContainerShellWithTwoSubmodels = ItemContainer.builder() .shell(shell("", shellDescriptor( List.of(submodelDescriptorWithDspEndpoint( - "urn:bamm:com.catenax.serial_part_typization:1.0.0#SerialPartTypization", + serialPartAspectName, "testSerialPartTypizationEndpoint"), submodelDescriptorWithDspEndpoint( - "urn:bamm:com.catenax.assembly_part_relationship:1.0.0#AssemblyPartRelationship", + singleLevelBomAsBuiltAspectName, "testAssemblyPartRelationshipEndpoint"))))); // when @@ -92,10 +94,10 @@ void shouldCatchJsonParseExceptionAndPutTombstone() throws SchemaNotFoundExcepti final ItemContainer.ItemContainerBuilder itemContainerShellWithTwoSubmodels = ItemContainer.builder() .shell(shell("", shellDescriptor( List.of(submodelDescriptorWithDspEndpoint( - "urn:bamm:com.catenax.serial_part:1.0.0#SerialPart", + serialPartAspectName, "testSerialPartEndpoint"), submodelDescriptorWithDspEndpoint( - "urn:bamm:com.catenax.single_level_bom_as_built:1.0.0#SingleLevelBomAsBuilt", + singleLevelBomAsBuiltAspectName, "testSingleLevelBomAsBuiltEndpoint"))))); // when @@ -117,10 +119,10 @@ void shouldPutTombstoneForMissingBpn() { final ItemContainer.ItemContainerBuilder itemContainerShellWithTwoSubmodels = ItemContainer.builder() .shell(shell("", shellDescriptor( List.of(submodelDescriptorWithDspEndpoint( - "urn:bamm:com.catenax.serial_part:1.0.0#SerialPart", + serialPartAspectName, "testSerialPartEndpoint"), submodelDescriptorWithDspEndpoint( - "urn:bamm:com.catenax.single_level_bom_as_built:1.0.0#SingleLevelBomAsBuilt", + singleLevelBomAsBuiltAspectName, "testSingleLevelBomAsBuiltEndpoint"))))); // when @@ -143,10 +145,10 @@ void shouldCatchUsagePolicyExceptionAndPutTombstone() throws EdcClientException final ItemContainer.ItemContainerBuilder itemContainerShellWithTwoSubmodels = ItemContainer.builder() .shell(shell("", shellDescriptor( List.of(submodelDescriptorWithDspEndpoint( - "urn:bamm:com.catenax.serial_part:1.0.0#SerialPart", + serialPartAspectName, "testSerialPartEndpoint"), submodelDescriptorWithDspEndpoint( - "urn:bamm:com.catenax.single_level_bom_as_built:1.0.0#SingleLevelBomAsBuilt", + singleLevelBomAsBuiltAspectName, "testSingleLevelBomAsBuiltEndpoint"))))); // when @@ -170,7 +172,7 @@ void shouldRequestForAllEndpoints() throws EdcClientException, InvalidSchemaExce final ItemContainer.ItemContainerBuilder itemContainerShellWithOneSubmodel = ItemContainer.builder() .shell(shell("", shellDescriptor( List.of(submodelDescriptor( - "urn:bamm:com.catenax.serial_part:1.0.0#SerialPart", + serialPartAspectName, "testSerialPartEndpoint", ""))))); @@ -188,8 +190,7 @@ void shouldRequestForAllEndpoints() throws EdcClientException, InvalidSchemaExce // then assertThat(result).isNotNull(); assertThat(result.getSubmodels()).hasSize(1); - assertThat(result.getSubmodels().get(0).getAspectType()).isEqualTo( - "urn:bamm:com.catenax.serial_part:1.0.0#SerialPart"); + assertThat(result.getSubmodels().get(0).getAspectType()).isEqualTo(serialPartAspectName); assertThat(result.getSubmodels().get(0).getContractAgreementId()).isNull(); assertThat(result.getTombstones()).isEmpty(); } @@ -200,10 +201,10 @@ void shouldCatchRestClientExceptionAndPutTombstone() throws SchemaNotFoundExcept final ItemContainer.ItemContainerBuilder itemContainerShellWithTwoSubmodels = ItemContainer.builder() .shell(shell("", shellDescriptor( List.of(submodelDescriptorWithDspEndpoint( - "urn:bamm:com.catenax.serial_part:1.0.0#SerialPart", + serialPartAspectName, "testSerialPartEndpoint"), submodelDescriptorWithDspEndpoint( - "urn:bamm:com.catenax.single_level_bom_as_built:1.0.0#SingleLevelBomAsBuilt", + singleLevelBomAsBuiltAspectName, "testSingleLevelBomAsBuiltEndpoint"))))); // when diff --git a/irs-api/src/test/java/org/eclipse/tractusx/irs/services/IrsItemGraphQueryServiceSpringBootTest.java b/irs-api/src/test/java/org/eclipse/tractusx/irs/services/IrsItemGraphQueryServiceSpringBootTest.java index 94bc7fa710..3e287de5b4 100644 --- a/irs-api/src/test/java/org/eclipse/tractusx/irs/services/IrsItemGraphQueryServiceSpringBootTest.java +++ b/irs-api/src/test/java/org/eclipse/tractusx/irs/services/IrsItemGraphQueryServiceSpringBootTest.java @@ -25,15 +25,17 @@ import static org.assertj.core.api.Assertions.assertThat; import static org.awaitility.Awaitility.given; +import static org.eclipse.tractusx.irs.util.TestMother.productDescriptionAspectName; import static org.eclipse.tractusx.irs.util.TestMother.registerJob; import static org.eclipse.tractusx.irs.util.TestMother.registerJobWithDepthAndAspect; import static org.eclipse.tractusx.irs.util.TestMother.registerJobWithDepthAndAspectAndCollectAspects; import static org.eclipse.tractusx.irs.util.TestMother.registerJobWithDirection; import static org.eclipse.tractusx.irs.util.TestMother.registerJobWithoutDepth; +import static org.eclipse.tractusx.irs.util.TestMother.serialPartAspectName; +import static org.eclipse.tractusx.irs.util.TestMother.singleLevelBomAsBuiltAspectName; import static org.hamcrest.Matchers.greaterThan; import static org.junit.jupiter.api.Assertions.assertThrows; import static org.mockito.ArgumentMatchers.any; -import static org.mockito.Mockito.mock; import static org.mockito.Mockito.when; import java.time.ZonedDateTime; @@ -70,11 +72,6 @@ import org.springframework.boot.test.context.SpringBootTest; import org.springframework.boot.test.mock.mockito.MockBean; import org.springframework.context.annotation.Import; -import org.springframework.security.core.authority.SimpleGrantedAuthority; -import org.springframework.security.core.context.SecurityContext; -import org.springframework.security.core.context.SecurityContextHolder; -import org.springframework.security.oauth2.jwt.Jwt; -import org.springframework.security.oauth2.server.resource.authentication.JwtAuthenticationToken; import org.springframework.test.context.ActiveProfiles; import org.springframework.web.server.ResponseStatusException; @@ -104,18 +101,12 @@ class IrsItemGraphQueryServiceSpringBootTest { @MockBean private ConnectorEndpointsService connectorEndpointsService; - private static AspectModel getAspectModel(final String aspect, final String urn) { - return AspectModel.builder().name(aspect).urn(urn).build(); - } - @BeforeEach void setUp() throws SchemaNotFoundException { final List models = List.of( - getAspectModel(AspectType.SERIAL_PART.toString(), "urn:bamm:io.catenax.serial_part:1.0.0#SerialPart"), - getAspectModel(AspectType.PRODUCT_DESCRIPTION.toString(), - "urn:bamm:io.catenax.vehicle.product_description:2.0.0#ProductDescription"), - getAspectModel(AspectType.SINGLE_LEVEL_BOM_AS_BUILT.toString(), - "urn:bamm:io.catenax.single_level_bom_as_built:1.0.0#SingleLevelBomAsBuilt")); + getAspectModel(AspectType.SERIAL_PART.toString(), serialPartAspectName), + getAspectModel(AspectType.PRODUCT_DESCRIPTION.toString(), productDescriptionAspectName), + getAspectModel(AspectType.SINGLE_LEVEL_BOM_AS_BUILT.toString(), singleLevelBomAsBuiltAspectName)); final AspectModels aspectModels = new AspectModels(models, "2023-02-13T08:18:11.990659500Z"); when(semanticsHubFacade.getAllAspectModels()).thenReturn(aspectModels); } @@ -144,8 +135,8 @@ void registerJobWithCollectAspectsShouldIncludeSubmodels() throws InvalidSchemaE when(connectorEndpointsService.fetchConnectorEndpoints(any())).thenReturn( List.of("https://connector.endpoint.nl")); final RegisterJob registerJob = registerJob("urn:uuid:8ddd8fe0-1b4f-44b4-90f3-a8f68e551ac7", 100, - List.of(AspectType.SERIAL_PART.toString(), AspectType.PRODUCT_DESCRIPTION.toString(), - AspectType.SINGLE_LEVEL_BOM_AS_BUILT.toString()), true, false, Direction.DOWNWARD); + List.of(serialPartAspectName, productDescriptionAspectName, singleLevelBomAsBuiltAspectName), + true, false, Direction.DOWNWARD); when(connectorEndpointsService.fetchConnectorEndpoints(registerJob.getKey().getBpn())).thenReturn( List.of("singleLevelBomAsBuilt")); @@ -167,7 +158,7 @@ void registerJobShouldCreateTombstonesWhenNotPassingJsonSchemaValidation() throw List.of("https://connector.endpoint.nl")); final RegisterJob registerJob = registerJobWithDepthAndAspectAndCollectAspects(3, - List.of(AspectType.SINGLE_LEVEL_BOM_AS_BUILT.toString())); + List.of(singleLevelBomAsBuiltAspectName)); when(connectorEndpointsService.fetchConnectorEndpoints(registerJob.getKey().getBpn())).thenReturn( List.of("singleLevelBomAsBuilt")); @@ -184,10 +175,9 @@ void registerJobShouldCreateTombstonesWhenNotPassingJsonSchemaValidation() throw @Test void registerJobWithDepthShouldBuildTreeUntilGivenDepth() { // given - final RegisterJob registerJob = registerJobWithDepthAndAspect(1, null); + final RegisterJob registerJob = registerJobWithDepthAndAspect(1, List.of()); when(connectorEndpointsService.fetchConnectorEndpoints(any())).thenReturn( List.of("http://localhost/discovery")); - setSecurityContext(); // when final JobHandle registeredJob = service.registerItemJob(registerJob); @@ -239,8 +229,6 @@ void cancelJobById() { jobStore.create(multiTransferJob); - setSecurityContext(); - assertThat(service.cancelJobById(jobId)).isNotNull(); final Optional fetchedJob = jobStore.find(idAsString); @@ -256,8 +244,7 @@ void cancelJobById() { @Test void registerJobWithoutAspectsShouldUseDefault() { // given - final List emptyAspectTypeFilterList = List.of(); - final RegisterJob registerJob = registerJobWithDepthAndAspect(null, emptyAspectTypeFilterList); + final RegisterJob registerJob = registerJobWithDepthAndAspect(10, List.of()); when(connectorEndpointsService.fetchConnectorEndpoints(any())).thenReturn( List.of("http://localhost/discovery")); @@ -292,23 +279,10 @@ void shouldThrowIllegalArgumentExceptionForLifecycleAsSpecifiedAndDirectionUpwar } private int getRelationshipsSize(final UUID jobId) { - setSecurityContext(); return service.getJobForJobId(jobId, false).getRelationships().size(); } - private static void setSecurityContext() { - JwtAuthenticationToken jwtAuthenticationToken = mock(JwtAuthenticationToken.class); - Jwt token = mock(Jwt.class); - when(jwtAuthenticationToken.getAuthorities()).thenReturn(List.of(new SimpleGrantedAuthority("admin_irs"))); - when(jwtAuthenticationToken.getToken()).thenReturn(token); - when(token.getClaim("clientId")).thenReturn("test-client-id"); - SecurityContext securityContext = mock(SecurityContext.class); - when(securityContext.getAuthentication()).thenReturn(jwtAuthenticationToken); - SecurityContextHolder.setContext(securityContext); - } - private int getSubmodelsSize(final UUID jobId) { - setSecurityContext(); return service.getJobForJobId(jobId, false).getSubmodels().size(); } @@ -334,8 +308,11 @@ void checkMetricsRecordingTest() { } private int getTombstonesSize(final UUID jobId) { - setSecurityContext(); return service.getJobForJobId(jobId, false).getTombstones().size(); } + private static AspectModel getAspectModel(final String aspect, final String urn) { + return AspectModel.builder().name(aspect).urn(urn).build(); + } + } \ No newline at end of file diff --git a/irs-api/src/test/java/org/eclipse/tractusx/irs/util/TestMother.java b/irs-api/src/test/java/org/eclipse/tractusx/irs/util/TestMother.java index 9d9ac34dee..cb612a7ee7 100644 --- a/irs-api/src/test/java/org/eclipse/tractusx/irs/util/TestMother.java +++ b/irs-api/src/test/java/org/eclipse/tractusx/irs/util/TestMother.java @@ -52,7 +52,6 @@ import org.eclipse.tractusx.irs.component.assetadministrationshell.Reference; import org.eclipse.tractusx.irs.component.assetadministrationshell.SemanticId; import org.eclipse.tractusx.irs.component.assetadministrationshell.SubmodelDescriptor; -import org.eclipse.tractusx.irs.component.enums.AspectType; import org.eclipse.tractusx.irs.component.enums.BomLifecycle; import org.eclipse.tractusx.irs.component.enums.Direction; import org.eclipse.tractusx.irs.component.enums.JobState; @@ -72,6 +71,13 @@ */ public class TestMother { + public static final String singleLevelBomAsBuiltAspectName = "urn:bamm:io.catenax.single_level_bom_as_built:2.0.0#SingleLevelBomAsBuilt"; + public static final String singleLevelUsageAsBuiltAspectName = "urn:bamm:io.catenax.single_level_usage_as_built:2.0.0#SingleLevelUsageAsBuilt"; + public static final String serialPartAspectName = "urn:bamm:io.catenax.serial_part:1.0.1#SerialPart"; + public static final String batchAspectName = "urn:samm:io.catenax.batch:2.0.0#Batch"; + public static final String materialForRecyclingAspectName = "urn:bamm:io.catenax.material_for_recycling:1.1.0#MaterialForRecycling"; + public static final String productDescriptionAspectName = "urn:bamm:io.catenax.vehicle.product_description:2.0.0#ProductDescription"; + Faker faker = new Faker(); public static RegisterJob registerJobWithoutDepthAndAspect() { @@ -79,7 +85,7 @@ public static RegisterJob registerJobWithoutDepthAndAspect() { } public static RegisterJob registerJobWithoutDepth() { - return registerJobWithDepthAndAspect(null, List.of(AspectType.SINGLE_LEVEL_BOM_AS_BUILT.toString())); + return registerJobWithDepthAndAspect(null, List.of(singleLevelBomAsBuiltAspectName)); } public static RegisterJob registerJobWithDepthAndAspect(final Integer depth, final List aspectTypes) { @@ -106,7 +112,7 @@ public static RegisterJob registerJobWithDepthAndAspectAndCollectAspects(final I public static RegisterJob registerJobWithLookupBPNs() { return registerJob("urn:uuid:8ddd8fe0-1b4f-44b4-90f3-a8f68e551ac7", null, - List.of(AspectType.SINGLE_LEVEL_BOM_AS_BUILT.toString()), false, true, Direction.DOWNWARD); + List.of(singleLevelBomAsBuiltAspectName), false, true, Direction.DOWNWARD); } public static RegisterJob registerJob(final String globalAssetId, final Integer depth, @@ -153,8 +159,7 @@ public static JobParameter jobParameter() { .depth(5) .bomLifecycle(BomLifecycle.AS_BUILT) .direction(Direction.DOWNWARD) - .aspects(List.of(AspectType.SERIAL_PART.toString(), - AspectType.SINGLE_LEVEL_BOM_AS_BUILT.toString())) + .aspects(List.of(serialPartAspectName, singleLevelBomAsBuiltAspectName)) .auditContractNegotiation(false) .build(); } @@ -164,8 +169,7 @@ public static JobParameter jobParameterUpward() { .depth(0) .bomLifecycle(BomLifecycle.AS_BUILT) .direction(Direction.UPWARD) - .aspects(List.of(AspectType.SERIAL_PART.toString(), - AspectType.SINGLE_LEVEL_USAGE_AS_BUILT.toString())) + .aspects(List.of(serialPartAspectName, singleLevelBomAsBuiltAspectName)) .build(); } @@ -173,8 +177,7 @@ public static JobParameter jobParameterCollectAspects() { return JobParameter.builder() .depth(0) .bomLifecycle(BomLifecycle.AS_BUILT) - .aspects(List.of(AspectType.SERIAL_PART.toString(), - AspectType.SINGLE_LEVEL_BOM_AS_BUILT.toString())) + .aspects(List.of(serialPartAspectName, singleLevelBomAsBuiltAspectName)) .collectAspects(true) .build(); } @@ -183,7 +186,7 @@ public static JobParameter jobParameterFilter() { return JobParameter.builder() .depth(0) .bomLifecycle(BomLifecycle.AS_BUILT) - .aspects(List.of(AspectType.MATERIAL_FOR_RECYCLING.toString())) + .aspects(List.of(materialForRecyclingAspectName)) .build(); } @@ -192,8 +195,7 @@ public static JobParameter jobParameterCollectBpns() { .depth(0) .bomLifecycle(BomLifecycle.AS_BUILT) .direction(Direction.DOWNWARD) - .aspects(List.of(AspectType.SERIAL_PART.toString(), - AspectType.SINGLE_LEVEL_BOM_AS_BUILT.toString())) + .aspects(List.of(serialPartAspectName, singleLevelBomAsBuiltAspectName)) .lookupBPNs(true) .build(); } @@ -203,8 +205,7 @@ public static JobParameter jobParameterAuditContractNegotiation() { .depth(5) .bomLifecycle(BomLifecycle.AS_BUILT) .direction(Direction.DOWNWARD) - .aspects(List.of(AspectType.SERIAL_PART.toString(), - AspectType.SINGLE_LEVEL_BOM_AS_BUILT.toString())) + .aspects(List.of(serialPartAspectName, singleLevelBomAsBuiltAspectName)) .auditContractNegotiation(true) .build(); } diff --git a/irs-api/src/test/resources/__files/semantichub/all-models-page-IT.json b/irs-api/src/test/resources/__files/semantichub/all-models-page-IT.json index 36d2bd6a4a..482990f61a 100644 --- a/irs-api/src/test/resources/__files/semantichub/all-models-page-IT.json +++ b/irs-api/src/test/resources/__files/semantichub/all-models-page-IT.json @@ -532,6 +532,13 @@ "type": "BAMM", "status": "DEPRECATED" }, + { + "urn": "urn:bamm:io.catenax.single_level_bom_as_built:2.0.0#SingleLevelBomAsBuilt", + "version": "2.0.0", + "name": "SingleLevelBomAsBuilt", + "type": "BAMM", + "status": "RELEASED" + }, { "urn": "urn:samm:io.catenax.batch:2.0.0#Batch", "version": "2.0.0", diff --git a/irs-common/pom.xml b/irs-common/pom.xml index 8af8b54cc3..3264382522 100644 --- a/irs-common/pom.xml +++ b/irs-common/pom.xml @@ -53,10 +53,6 @@ org.springframework.boot spring-boot-starter-security - - org.springframework.boot - spring-boot-starter-oauth2-resource-server - io.github.resilience4j diff --git a/irs-models/pom.xml b/irs-models/pom.xml index c66b493ff3..7c8585f3d3 100644 --- a/irs-models/pom.xml +++ b/irs-models/pom.xml @@ -77,6 +77,11 @@ snakeyaml ${snakeyaml.version} + + com.vdurmont + semver4j + 3.1.0 + org.projectlombok lombok diff --git a/irs-models/src/main/java/org/eclipse/tractusx/irs/component/RegisterBatchOrder.java b/irs-models/src/main/java/org/eclipse/tractusx/irs/component/RegisterBatchOrder.java index a70188eec8..8617dc210e 100644 --- a/irs-models/src/main/java/org/eclipse/tractusx/irs/component/RegisterBatchOrder.java +++ b/irs-models/src/main/java/org/eclipse/tractusx/irs/component/RegisterBatchOrder.java @@ -55,6 +55,7 @@ import jakarta.validation.constraints.Max; import jakarta.validation.constraints.Min; import jakarta.validation.constraints.NotEmpty; +import jakarta.validation.constraints.Pattern; import lombok.AccessLevel; import lombok.AllArgsConstructor; import lombok.Builder; @@ -79,6 +80,8 @@ }) public class RegisterBatchOrder { + private static final String ASPECT_MODEL_REGEX = "^(urn:(b|s)amm:.*\\d\\.\\d\\.\\d)?(#)?(\\w+)?$"; + @NotEmpty @Valid @ArraySchema(schema = @Schema(description = "Keys array contains required attributes for identify part chain entry node ", implementation = PartChainIdentificationKey.class), maxItems = Integer.MAX_VALUE) @@ -87,8 +90,9 @@ public class RegisterBatchOrder { @Schema(description = "BoM Lifecycle of the result tree.", implementation = BomLifecycle.class) private BomLifecycle bomLifecycle; - @ArraySchema(schema = @Schema(implementation = String.class), maxItems = Integer.MAX_VALUE) - private List aspects; + @ArraySchema(arraySchema = @Schema(description = "List of aspect names that will be collected if \\ flag is set to true.", example = "urn:samm:io.catenax.single_level_bom_as_built:2.0.0#SingleLevelBomAsBuilt", + implementation = String.class, pattern = ASPECT_MODEL_REGEX), maxItems = Integer.MAX_VALUE) + private List<@Pattern(regexp = ASPECT_MODEL_REGEX) String> aspects; @Schema(implementation = Integer.class, minimum = MIN_TREE_DEPTH_DESC, maximum = MAX_TREE_DEPTH_DESC, description = "Max depth of the item graph returned. If no depth is set item graph with max depth is returned.") diff --git a/irs-models/src/main/java/org/eclipse/tractusx/irs/component/RegisterBpnInvestigationJob.java b/irs-models/src/main/java/org/eclipse/tractusx/irs/component/RegisterBpnInvestigationJob.java index 7d66c465af..f765410fce 100644 --- a/irs-models/src/main/java/org/eclipse/tractusx/irs/component/RegisterBpnInvestigationJob.java +++ b/irs-models/src/main/java/org/eclipse/tractusx/irs/component/RegisterBpnInvestigationJob.java @@ -57,7 +57,7 @@ public class RegisterBpnInvestigationJob { private PartChainIdentificationKey key; @NotEmpty - @ArraySchema(schema = @Schema(description = "Array of BPNS numbers.", example = "BPNS000000000DDD", + @ArraySchema(arraySchema = @Schema(description = "Array of BPNS numbers.", example = "BPNS000000000DDD", implementation = String.class, pattern = BPN_REGEX), maxItems = Integer.MAX_VALUE) private List<@Pattern(regexp = BPN_REGEX) String> incidentBPNSs; diff --git a/irs-models/src/main/java/org/eclipse/tractusx/irs/component/RegisterJob.java b/irs-models/src/main/java/org/eclipse/tractusx/irs/component/RegisterJob.java index 82b978ab50..a6edfecda2 100644 --- a/irs-models/src/main/java/org/eclipse/tractusx/irs/component/RegisterJob.java +++ b/irs-models/src/main/java/org/eclipse/tractusx/irs/component/RegisterJob.java @@ -31,6 +31,7 @@ import jakarta.validation.constraints.Max; import jakarta.validation.constraints.Min; import jakarta.validation.constraints.NotNull; +import jakarta.validation.constraints.Pattern; import lombok.AllArgsConstructor; import lombok.Builder; import lombok.Data; @@ -53,7 +54,7 @@ public class RegisterJob { private static final String MAX_TREE_DEPTH_DESC = "100"; private static final int MIN_TREE_DEPTH = 1; private static final int MAX_TREE_DEPTH = 100; - private static final String ASPECT_MODEL_REGEX = "^(urn:bamm:.*\\d\\.\\d\\.\\d)?(#)?(\\w+)?$"; + private static final String ASPECT_MODEL_REGEX = "^(urn:(b|s)amm:.*\\d\\.\\d\\.\\d)?(#)?(\\w+)?$"; @NotNull @Valid @@ -63,8 +64,9 @@ public class RegisterJob { @Schema(description = "BoM Lifecycle of the result tree.", implementation = BomLifecycle.class) private BomLifecycle bomLifecycle; - @ArraySchema(arraySchema = @Schema(implementation = String.class, description = "List of aspect names that will be collected if \\ flag is set to true."), maxItems = Integer.MAX_VALUE) - private List aspects; + @ArraySchema(arraySchema = @Schema(description = "List of aspect names that will be collected if \\ flag is set to true.", example = "urn:samm:io.catenax.single_level_bom_as_built:2.0.0#SingleLevelBomAsBuilt", + implementation = String.class, pattern = ASPECT_MODEL_REGEX), maxItems = Integer.MAX_VALUE) + private List<@Pattern(regexp = ASPECT_MODEL_REGEX) String> aspects; @Schema(implementation = Integer.class, minimum = MIN_TREE_DEPTH_DESC, maximum = MAX_TREE_DEPTH_DESC, description = "Max depth of the item graph returned. If no depth is set item graph with max depth is returned.") diff --git a/irs-models/src/main/java/org/eclipse/tractusx/irs/component/assetadministrationshell/AssetAdministrationShellDescriptor.java b/irs-models/src/main/java/org/eclipse/tractusx/irs/component/assetadministrationshell/AssetAdministrationShellDescriptor.java index 8867d83e0d..f9c29d5329 100644 --- a/irs-models/src/main/java/org/eclipse/tractusx/irs/component/assetadministrationshell/AssetAdministrationShellDescriptor.java +++ b/irs-models/src/main/java/org/eclipse/tractusx/irs/component/assetadministrationshell/AssetAdministrationShellDescriptor.java @@ -25,7 +25,6 @@ import java.util.Collection; import java.util.List; -import java.util.Locale; import java.util.Optional; import io.swagger.v3.oas.annotations.media.ArraySchema; @@ -91,10 +90,10 @@ public class AssetAdministrationShellDescriptor { * @return ManufacturerId value from Specific Asset Ids */ public Optional findManufacturerId() { - return this.specificAssetIds.stream() - .filter(assetId -> "ManufacturerId".equalsIgnoreCase(assetId.getName())) - .map(IdentifierKeyValuePair::getValue) - .findFirst(); + return specificAssetIds.stream() + .filter(assetId -> "ManufacturerId".equalsIgnoreCase(assetId.getName())) + .map(IdentifierKeyValuePair::getValue) + .findFirst(); } /** @@ -102,7 +101,7 @@ public Optional findManufacturerId() { * @return AssetAdministrationShellDescriptor with filtered submodel descriptors */ public AssetAdministrationShellDescriptor withFilteredSubmodelDescriptors(final List aspectTypes) { - this.setSubmodelDescriptors(this.filterDescriptorsByAspectTypes(aspectTypes)); + setSubmodelDescriptors(filterDescriptorsByAspectTypes(aspectTypes)); return this; } @@ -125,29 +124,9 @@ public List findRelationshipEndpointAddresses(final AspectType relatio */ public List filterDescriptorsByAspectTypes(final List aspectTypes) { log.info("Filtering for Aspect Types '{}'", aspectTypes); - return this.submodelDescriptors.stream() - .filter(submodelDescriptor -> aspectTypes.stream() - .anyMatch(type -> isMatching( - submodelDescriptor, type))) - - .toList(); - } - - private boolean isMatching(final SubmodelDescriptor submodelDescriptor, final String aspectTypeFilter) { - final Optional submodelAspectType = Optional.ofNullable(submodelDescriptor.getSemanticId().getKeys()) - .flatMap(key -> key.stream().findFirst()) - .map(SemanticId::getValue); - return submodelAspectType.map( - semanticId -> semanticId.endsWith("#" + aspectTypeFilter) || contains(semanticId, aspectTypeFilter) - || semanticId.equals(aspectTypeFilter)).orElse(false); - } - - private boolean contains(final String semanticId, final String aspectTypeFilter) { - // https://stackoverflow.com/a/3752693 - final String[] split = aspectTypeFilter.split("(?=\\p{Lu})"); - final String join = String.join("_", split).toLowerCase(Locale.ROOT); - log.debug("lower case aspect: '{}'", join); - return semanticId.contains(join); + return submodelDescriptors.stream() + .filter(submodelDescriptor -> aspectTypes.stream().anyMatch(submodelDescriptor::isAspect)) + .toList(); } } diff --git a/irs-models/src/main/java/org/eclipse/tractusx/irs/component/assetadministrationshell/SemanticModel.java b/irs-models/src/main/java/org/eclipse/tractusx/irs/component/assetadministrationshell/SemanticModel.java new file mode 100644 index 0000000000..be09941e4b --- /dev/null +++ b/irs-models/src/main/java/org/eclipse/tractusx/irs/component/assetadministrationshell/SemanticModel.java @@ -0,0 +1,51 @@ +/******************************************************************************** + * Copyright (c) 2022,2024 Bayerische Motoren Werke Aktiengesellschaft (BMW AG) + * Copyright (c) 2021,2024 Contributors to the Eclipse Foundation + * + * See the NOTICE file(s) distributed with this work for additional + * information regarding copyright ownership. + * + * This program and the accompanying materials are made available under the + * terms of the Apache License, Version 2.0 which is available at + * https://www.apache.org/licenses/LICENSE-2.0. + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, WITHOUT + * WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the + * License for the specific language governing permissions and limitations + * under the License. + * + * SPDX-License-Identifier: Apache-2.0 + ********************************************************************************/ +package org.eclipse.tractusx.irs.component.assetadministrationshell; + +import com.vdurmont.semver4j.Semver; + +record SemanticModel(String urn, String type, String name, Semver version) { + + private static final int VALID_SEMANTIC_ID_LENGTH = 4; + + static SemanticModel parse(final String semanticId) { + final String[] parts = semanticId.split(":"); + if (parts.length != VALID_SEMANTIC_ID_LENGTH) { + throw new IllegalArgumentException("Invalid semanticId value, cant parse: " + semanticId); + } + + final String version = parts[3].split("#")[0]; + return new SemanticModel(parts[0], parts[1], parts[2], new Semver(version)); + } + + boolean matches(final SemanticModel model) { + return urn.equals(model.urn) + && type.equals(model.type) + && name.equals(model.name) + && versionIsInRange(model); + } + + private boolean versionIsInRange(final SemanticModel model) { + final Semver nextMajor = model.version.nextMajor(); + final Semver minMajor = new Semver(model.version.getMajor() + ".0.0"); + + return version.isGreaterThanOrEqualTo(minMajor) && version.isLowerThan(nextMajor); + } +} diff --git a/irs-models/src/main/java/org/eclipse/tractusx/irs/component/assetadministrationshell/SubmodelDescriptor.java b/irs-models/src/main/java/org/eclipse/tractusx/irs/component/assetadministrationshell/SubmodelDescriptor.java index ee20cce9e1..1e8528751d 100644 --- a/irs-models/src/main/java/org/eclipse/tractusx/irs/component/assetadministrationshell/SubmodelDescriptor.java +++ b/irs-models/src/main/java/org/eclipse/tractusx/irs/component/assetadministrationshell/SubmodelDescriptor.java @@ -24,8 +24,11 @@ package org.eclipse.tractusx.irs.component.assetadministrationshell; import java.util.List; +import java.util.Locale; +import java.util.Optional; import com.fasterxml.jackson.annotation.JsonIgnore; +import com.vdurmont.semver4j.SemverException; import io.swagger.v3.oas.annotations.media.ArraySchema; import lombok.Builder; import lombok.Data; @@ -72,7 +75,28 @@ public class SubmodelDescriptor { */ @JsonIgnore public String getAspectType() { - return this.getSemanticId().getKeys().stream().findFirst().map(SemanticId::getValue).orElse(null); + return getSemanticId().getKeys().stream().findFirst().map(SemanticId::getValue).orElse(null); } + /* package */ boolean isAspect(final String filterSemanticId) { + return Optional.ofNullable(getAspectType()) + .map(semanticId -> semanticId.contains(lowerCaseNameWithUnderscores(filterSemanticId)) + || semanticModelNamesMatchAndVersionIsInRange(semanticId, filterSemanticId)) + .orElse(false); + } + + private String lowerCaseNameWithUnderscores(final String filterSemanticId) { + return String.join("_", filterSemanticId.split("(?=[A-Z])")).toLowerCase(Locale.ROOT); + } + + private boolean semanticModelNamesMatchAndVersionIsInRange(final String semanticId, final String filterSemanticId) { + try { + final SemanticModel submodel = SemanticModel.parse(semanticId); + final SemanticModel filter = SemanticModel.parse(filterSemanticId); + + return filter.matches(submodel); + } catch (final IllegalArgumentException | SemverException e) { + return false; + } + } } diff --git a/irs-registry-client/src/main/java/org/eclipse/tractusx/irs/registryclient/central/AssetAdministrationShellTestdataCreator.java b/irs-registry-client/src/main/java/org/eclipse/tractusx/irs/registryclient/central/AssetAdministrationShellTestdataCreator.java index 7d83cb19d9..4c0316fbab 100644 --- a/irs-registry-client/src/main/java/org/eclipse/tractusx/irs/registryclient/central/AssetAdministrationShellTestdataCreator.java +++ b/irs-registry-client/src/main/java/org/eclipse/tractusx/irs/registryclient/central/AssetAdministrationShellTestdataCreator.java @@ -114,53 +114,53 @@ public AssetAdministrationShellDescriptor createDummyAssetAdministrationShellDes } private SubmodelDescriptor createSingleLevelBomAsBuiltSubmodelDescriptor(final String catenaXId) { - return createSubmodelDescriptor(catenaXId, "urn:bamm:io.catenax.single_level_bom_as_built:1.0.0", + return createSubmodelDescriptor(catenaXId, "urn:bamm:io.catenax.single_level_bom_as_built:2.0.0#SingleLevelBomAsBuilt", "singleLevelBomAsBuilt"); } private SubmodelDescriptor createSingleLevelUsageAsBuiltSubmodelDescriptor(final String catenaXId) { - return createSubmodelDescriptor(catenaXId, "urn:bamm:io.catenax.single_level_usage_as_built:1.0.0", + return createSubmodelDescriptor(catenaXId, "urn:bamm:io.catenax.single_level_usage_as_built:2.0.0#SingleLevelUsageAsBuilt", "singleLevelUsageAsBuilt"); } private SubmodelDescriptor createSingleLevelBomAsSpecifiedSubmodelDescriptor(final String catenaXId) { - return createSubmodelDescriptor(catenaXId, "urn:bamm:io.catenax.single_level_bom_as_specified:1.0.0", + return createSubmodelDescriptor(catenaXId, "urn:bamm:io.catenax.single_level_bom_as_specified:1.0.0#SingleLevelBomAsSpecified", "singleLevelBomAsSpecified"); } private SubmodelDescriptor createSerialPartSubmodelDescriptor(final String catenaXId) { - return createSubmodelDescriptor(catenaXId, "urn:bamm:io.catenax.serial_part:1.0.0", "serialPart"); + return createSubmodelDescriptor(catenaXId, "urn:bamm:io.catenax.serial_part:1.0.1#SerialPart", "serialPart"); } private SubmodelDescriptor createSingleLevelBomAsPlannedSubmodelDescriptor(final String catenaXId) { - return createSubmodelDescriptor(catenaXId, "urn:bamm:io.catenax.single_level_bom_as_planned:1.0.0", + return createSubmodelDescriptor(catenaXId, "urn:bamm:io.catenax.single_level_bom_as_planned:2.0.0#SingleLevelBomAsPlanned", "singleLevelBomAsPlanned"); } private SubmodelDescriptor createPartAsPlannedSubmodelDescriptor(final String catenaXId) { - return createSubmodelDescriptor(catenaXId, "urn:bamm:io.catenax.part_as_planned:1.0.0", "partAsPlanned"); + return createSubmodelDescriptor(catenaXId, "urn:bamm:io.catenax.part_as_planned:1.0.1#PartAsPlanned", "partAsPlanned"); } private SubmodelDescriptor createBatchSubmodelDescriptor(final String catenaXId) { - return createSubmodelDescriptor(catenaXId, "urn:bamm:io.catenax.batch:1.0.0", "batch"); + return createSubmodelDescriptor(catenaXId, "urn:bamm:io.catenax.batch:2.0.0#Batch", "batch"); } private SubmodelDescriptor createMaterialForRecyclingSubmodelDescriptor(final String catenaXId) { - return createSubmodelDescriptor(catenaXId, "urn:bamm:io.catenax.material_for_recycling:1.0.0", + return createSubmodelDescriptor(catenaXId, "urn:bamm:io.catenax.material_for_recycling:1.1.0#MaterialForRecycling", "materialForRecycling"); } private SubmodelDescriptor createProductDescriptionSubmodelDescriptor(final String catenaXId) { - return createSubmodelDescriptor(catenaXId, "urn:bamm:io.catenax.product_description:1.0.0", + return createSubmodelDescriptor(catenaXId, "urn:bamm:io.catenax.product_description:1.0.0#ProductDescription", "productDescription"); } private SubmodelDescriptor createPhysicalDimensionSubmodelDescriptor(final String catenaXId) { - return createSubmodelDescriptor(catenaXId, "urn:bamm:io.catenax.physical_dimension:1.0.0", "physicalDimension"); + return createSubmodelDescriptor(catenaXId, "urn:bamm:io.catenax.physical_dimension:1.0.0#PhysicalDimension", "physicalDimension"); } private SubmodelDescriptor createPartAsSpecifiedSubmodelDescriptor(final String catenaXId) { - return createSubmodelDescriptor(catenaXId, "urn:bamm:io.catenax.part_as_specified:1.0.0", + return createSubmodelDescriptor(catenaXId, "urn:bamm:io.catenax.part_as_specified:2.0.0#PartAsSpecified", "partAsSpecified"); } diff --git a/irs-registry-client/src/test/java/org/eclipse/tractusx/irs/registryclient/central/AssetAdministrationShellTestdataCreatorTest.java b/irs-registry-client/src/test/java/org/eclipse/tractusx/irs/registryclient/central/AssetAdministrationShellTestdataCreatorTest.java index 52d9f91433..acc0ed5269 100644 --- a/irs-registry-client/src/test/java/org/eclipse/tractusx/irs/registryclient/central/AssetAdministrationShellTestdataCreatorTest.java +++ b/irs-registry-client/src/test/java/org/eclipse/tractusx/irs/registryclient/central/AssetAdministrationShellTestdataCreatorTest.java @@ -64,8 +64,8 @@ void shouldReturnAssetAdministrationShellDescriptorWhenRequestingWithCatenaXId() assertThat(endpointAddress).isEqualTo("singleLevelBomAsBuilt"); assertThat(aasDescriptor.getSubmodelDescriptors().get(0).getEndpoints().get(0).getProtocolInformation().getSubprotocolBody()).contains(catenaXId); - assertThat(aasDescriptor.getSubmodelDescriptors().get(0).getSemanticId().getKeys().get(0).getValue()).isEqualTo("urn:bamm:io.catenax.single_level_bom_as_built:1.0.0"); - assertThat(aasDescriptor.getSubmodelDescriptors().get(1).getSemanticId().getKeys().get(0).getValue()).isEqualTo("urn:bamm:io.catenax.serial_part:1.0.0"); + assertThat(aasDescriptor.getSubmodelDescriptors().get(0).getSemanticId().getKeys().get(0).getValue()).isEqualTo("urn:bamm:io.catenax.single_level_bom_as_built:2.0.0#SingleLevelBomAsBuilt"); + assertThat(aasDescriptor.getSubmodelDescriptors().get(1).getSemanticId().getKeys().get(0).getValue()).isEqualTo("urn:bamm:io.catenax.serial_part:1.0.1#SerialPart"); } @Test diff --git a/irs-registry-client/src/test/java/org/eclipse/tractusx/irs/registryclient/central/CentralDigitalTwinRegistryServiceTest.java b/irs-registry-client/src/test/java/org/eclipse/tractusx/irs/registryclient/central/CentralDigitalTwinRegistryServiceTest.java index 0d88ade1b3..96919bc530 100644 --- a/irs-registry-client/src/test/java/org/eclipse/tractusx/irs/registryclient/central/CentralDigitalTwinRegistryServiceTest.java +++ b/irs-registry-client/src/test/java/org/eclipse/tractusx/irs/registryclient/central/CentralDigitalTwinRegistryServiceTest.java @@ -57,8 +57,8 @@ @ExtendWith(MockitoExtension.class) class CentralDigitalTwinRegistryServiceTest extends LocalTestDataConfigurationAware { - private final String singleLevelBomAsBuiltURN = "urn:bamm:io.catenax.single_level_bom_as_built:1.0.0"; - private final String serialPartURN = "urn:bamm:io.catenax.serial_part:1.0.0"; + private final String singleLevelBomAsBuiltURN = "urn:bamm:io.catenax.single_level_bom_as_built:2.0.0#SingleLevelBomAsBuilt"; + private final String serialPartURN = "urn:bamm:io.catenax.serial_part:1.0.1#SerialPart"; private DigitalTwinRegistryService digitalTwinRegistryService; @Mock private DigitalTwinRegistryClient dtRegistryClientMock;