From b8a32556e8c3204d70c47ee97b70e30f9448eeca Mon Sep 17 00:00:00 2001 From: Matthias Fischer Date: Tue, 30 Jul 2024 13:28:05 +0200 Subject: [PATCH 01/27] chore(quality): [#841] remove obsolete method --- .../org/eclipse/tractusx/irs/component/Tombstone.java | 10 ---------- 1 file changed, 10 deletions(-) diff --git a/irs-models/src/main/java/org/eclipse/tractusx/irs/component/Tombstone.java b/irs-models/src/main/java/org/eclipse/tractusx/irs/component/Tombstone.java index 01a464eab..3b8a01e47 100644 --- a/irs-models/src/main/java/org/eclipse/tractusx/irs/component/Tombstone.java +++ b/irs-models/src/main/java/org/eclipse/tractusx/irs/component/Tombstone.java @@ -133,16 +133,6 @@ private static String getRootErrorMessages(final Throwable throwable) { return ExceptionUtils.getRootCauseMessage(throwable); } - private static ProcessingError withProcessingError(final ProcessStep processStep, final int retryCount, - final String exception) { - return ProcessingError.builder() - .withProcessStep(processStep) - .withRetryCounter(retryCount) - .withLastAttempt(ZonedDateTime.now(ZoneOffset.UTC)) - .withErrorDetail(exception) - .build(); - } - private static boolean hasSuppressedExceptions(final Throwable exception) { return exception.getSuppressed().length > 0; } From 39780ce7897b47b60fc7154ccf8a037d98688a5d Mon Sep 17 00:00:00 2001 From: Matthias Fischer Date: Tue, 30 Jul 2024 13:31:24 +0200 Subject: [PATCH 02/27] chore(quality): [#841] increase visibility of getRootErrorMessages --- .../main/java/org/eclipse/tractusx/irs/component/Tombstone.java | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/irs-models/src/main/java/org/eclipse/tractusx/irs/component/Tombstone.java b/irs-models/src/main/java/org/eclipse/tractusx/irs/component/Tombstone.java index 3b8a01e47..939f84abe 100644 --- a/irs-models/src/main/java/org/eclipse/tractusx/irs/component/Tombstone.java +++ b/irs-models/src/main/java/org/eclipse/tractusx/irs/component/Tombstone.java @@ -102,7 +102,7 @@ public static Tombstone from(final String globalAssetId, final String endpointUR .build(); } - private static List getRootErrorMessages(final Throwable... throwables) { + public static List getRootErrorMessages(final Throwable... throwables) { return Arrays.stream(throwables).map(Tombstone::getRootErrorMessages).toList(); } From 3cef7001775895ec6c2e396f6b98a7e1db3ff882 Mon Sep 17 00:00:00 2001 From: Matthias Fischer Date: Tue, 30 Jul 2024 13:37:17 +0200 Subject: [PATCH 03/27] chore(quality): [#841] mark unhandy construction methods as deprecated use builder instead, even though this will be more eloquent it will be more flexible and easier to understand and change --- .../tractusx/irs/component/Tombstone.java | 18 ++++++++++++++++-- 1 file changed, 16 insertions(+), 2 deletions(-) diff --git a/irs-models/src/main/java/org/eclipse/tractusx/irs/component/Tombstone.java b/irs-models/src/main/java/org/eclipse/tractusx/irs/component/Tombstone.java index 939f84abe..1d2f48627 100644 --- a/irs-models/src/main/java/org/eclipse/tractusx/irs/component/Tombstone.java +++ b/irs-models/src/main/java/org/eclipse/tractusx/irs/component/Tombstone.java @@ -60,11 +60,13 @@ public class Tombstone { private final ProcessingError processingError; private final Map policy; + @Deprecated // TODO (mfischer) remove this method, use builder instead public static Tombstone from(final String catenaXId, final String endpointURL, final Exception exception, final int retryCount, final ProcessStep processStep) { return from(catenaXId, endpointURL, exception.getMessage(), retryCount, processStep); } + @Deprecated // TODO (mfischer) remove this method, use builder instead public static Tombstone from(final String catenaXId, final String endpointURL, final Exception exception, final int retryCount, final ProcessStep processStep, final String businessPartnerNumber, final Map policy) { @@ -72,22 +74,34 @@ public static Tombstone from(final String catenaXId, final String endpointURL, f return Tombstone.builder() .endpointURL(endpointURL) .catenaXId(catenaXId) - .processingError(withProcessingError(processStep, retryCount, exception.getMessage())) + .processingError(ProcessingError.builder() + .withProcessStep(processStep) + .withRetryCounter(retryCount) + .withLastAttempt(ZonedDateTime.now(ZoneOffset.UTC)) + .withErrorDetail(exception.getMessage()) + .build()) .businessPartnerNumber(businessPartnerNumber) .policy(policy) .build(); } + @Deprecated // TODO (mfischer) remove this method, use builder instead public static Tombstone from(final String catenaXId, final String endpointURL, final String errorDetails, final int retryCount, final ProcessStep processStep) { return Tombstone.builder() .endpointURL(endpointURL) .catenaXId(catenaXId) - .processingError(withProcessingError(processStep, retryCount, errorDetails)) + .processingError(ProcessingError.builder() + .withProcessStep(processStep) + .withRetryCounter(retryCount) + .withLastAttempt(ZonedDateTime.now(ZoneOffset.UTC)) + .withErrorDetail(errorDetails) + .build()) .build(); } + @Deprecated // TODO (mfischer) remove this method, use builder instead public static Tombstone from(final String globalAssetId, final String endpointURL, final Throwable exception, final Throwable[] suppressed, final int retryCount, final ProcessStep processStep) { return Tombstone.builder() From a95e960ccc5b60b7ff898f436c6022f8dcb39dd6 Mon Sep 17 00:00:00 2001 From: Matthias Fischer Date: Tue, 30 Jul 2024 15:12:08 +0200 Subject: [PATCH 04/27] chore(quality): [#841] add bpn and edcUrl attributes to EdcRetrieverException, add builder --- .../decentral/EdcRetrieverException.java | 43 +++++++++++++++++++ .../decentral/EdcRetrieverExceptionTest.java | 43 +++++++++++++++++++ 2 files changed, 86 insertions(+) create mode 100644 irs-registry-client/src/test/java/org/eclipse/tractusx/irs/registryclient/decentral/EdcRetrieverExceptionTest.java diff --git a/irs-registry-client/src/main/java/org/eclipse/tractusx/irs/registryclient/decentral/EdcRetrieverException.java b/irs-registry-client/src/main/java/org/eclipse/tractusx/irs/registryclient/decentral/EdcRetrieverException.java index 71647bcf3..31230bdc2 100644 --- a/irs-registry-client/src/main/java/org/eclipse/tractusx/irs/registryclient/decentral/EdcRetrieverException.java +++ b/irs-registry-client/src/main/java/org/eclipse/tractusx/irs/registryclient/decentral/EdcRetrieverException.java @@ -23,11 +23,54 @@ ********************************************************************************/ package org.eclipse.tractusx.irs.registryclient.decentral; +import lombok.Getter; + /** * Thrown in case of error in EDC communication */ +@Getter public class EdcRetrieverException extends Exception { + + private String bpn; + + private String edcUrl; + + @Deprecated // TODO (mfischer) remove later, use the builder public EdcRetrieverException(final Throwable cause) { super(cause); } + + private EdcRetrieverException(final Builder builder) { + super(builder.cause); + this.bpn = builder.bpn; + this.edcUrl = builder.edcUrl; + } + + /** + * Builder for {@link EdcRetrieverException} + */ + public static class Builder { + private final Throwable cause; + private String bpn; + private String edcUrl; + + public Builder(final Throwable cause) { + this.cause = cause; + } + + public Builder withBpn(final String bpn) { + this.bpn = bpn; + return this; + } + + public Builder withEdcUrl(final String edcUrl) { + this.edcUrl = edcUrl; + return this; + } + + public EdcRetrieverException build() { + return new EdcRetrieverException(this); + } + } + } diff --git a/irs-registry-client/src/test/java/org/eclipse/tractusx/irs/registryclient/decentral/EdcRetrieverExceptionTest.java b/irs-registry-client/src/test/java/org/eclipse/tractusx/irs/registryclient/decentral/EdcRetrieverExceptionTest.java new file mode 100644 index 000000000..2a9c9cef5 --- /dev/null +++ b/irs-registry-client/src/test/java/org/eclipse/tractusx/irs/registryclient/decentral/EdcRetrieverExceptionTest.java @@ -0,0 +1,43 @@ +/******************************************************************************** + * 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.registryclient.decentral; + +import static org.assertj.core.api.Assertions.assertThat; + +import org.assertj.core.api.Assertions; +import org.junit.jupiter.api.Test; + +class EdcRetrieverExceptionTest { + + @Test + void test() throws EdcRetrieverException { + + final EdcRetrieverException build = new EdcRetrieverException.Builder( + new IllegalArgumentException("my illegal arg")).withEdcUrl("my url").withBpn("my bpn").build(); + + assertThat(build.getBpn()).isEqualTo("my bpn"); + assertThat(build.getEdcUrl()).isEqualTo("my url"); + + Assertions.assertThatThrownBy(() -> { + throw build; + }).hasMessageContaining("my illegal arg"); + + } +} \ No newline at end of file From f2d88aaf6962ccc99c6c5c7a36e0c1186e82084c Mon Sep 17 00:00:00 2001 From: Matthias Fischer Date: Wed, 31 Jul 2024 10:39:47 +0200 Subject: [PATCH 05/27] chore(quality): [#841] simplify builder for EdcReceiverException single field builder and private setters in the class are sufficient here and reduce amount of boilerplate code --- .../decentral/EdcRetrieverException.java | 22 ++++++++----------- 1 file changed, 9 insertions(+), 13 deletions(-) diff --git a/irs-registry-client/src/main/java/org/eclipse/tractusx/irs/registryclient/decentral/EdcRetrieverException.java b/irs-registry-client/src/main/java/org/eclipse/tractusx/irs/registryclient/decentral/EdcRetrieverException.java index 31230bdc2..94273d1a1 100644 --- a/irs-registry-client/src/main/java/org/eclipse/tractusx/irs/registryclient/decentral/EdcRetrieverException.java +++ b/irs-registry-client/src/main/java/org/eclipse/tractusx/irs/registryclient/decentral/EdcRetrieverException.java @@ -23,12 +23,15 @@ ********************************************************************************/ package org.eclipse.tractusx.irs.registryclient.decentral; +import lombok.AccessLevel; import lombok.Getter; +import lombok.Setter; /** * Thrown in case of error in EDC communication */ @Getter +@Setter(AccessLevel.PRIVATE) public class EdcRetrieverException extends Exception { private String bpn; @@ -40,36 +43,29 @@ public EdcRetrieverException(final Throwable cause) { super(cause); } - private EdcRetrieverException(final Builder builder) { - super(builder.cause); - this.bpn = builder.bpn; - this.edcUrl = builder.edcUrl; - } - /** * Builder for {@link EdcRetrieverException} */ public static class Builder { - private final Throwable cause; - private String bpn; - private String edcUrl; + + private final EdcRetrieverException exception; public Builder(final Throwable cause) { - this.cause = cause; + this.exception = new EdcRetrieverException(cause); } public Builder withBpn(final String bpn) { - this.bpn = bpn; + this.exception.setBpn(bpn); return this; } public Builder withEdcUrl(final String edcUrl) { - this.edcUrl = edcUrl; + this.exception.setEdcUrl(edcUrl); return this; } public EdcRetrieverException build() { - return new EdcRetrieverException(this); + return this.exception; } } From 543daf6923cff7fc82885180cbf2e6b1cd016b99 Mon Sep 17 00:00:00 2001 From: Matthias Fischer Date: Thu, 1 Aug 2024 01:44:44 +0200 Subject: [PATCH 06/27] fix(exception-handling): [#841] Fixes, improvements, test - Use the new EdcRetrieverException.Builder. - Add endpoint url(s) and BPN in some places. - Enhance tests. - Remove the constructor methods Tombstone.from. Builder is used now. - General code cleanup and formatting improvements across multiple test and implementation classes to enhance readability and maintainability including extraction of code to methods. --- .../job/delegate/AbstractDelegate.java | 2 +- .../job/delegate/DigitalTwinDelegate.java | 49 ++++++-- .../job/delegate/RelationshipDelegate.java | 76 +++++++++--- .../job/delegate/SubmodelDelegate.java | 73 ++++++++--- .../configuration/RegistryConfiguration.java | 2 +- .../bpn/validation/IncidentValidation.java | 26 +++- ...vestigationJobProcessingEventListener.java | 2 +- .../irs/IrsWireMockIntegrationTest.java | 13 ++ .../delegate/RelationshipDelegateTest.java | 116 ++++++++++-------- .../tractusx/irs/component/TombstoneTest.java | 72 ++++++++--- .../irs/edc/client/EdcCallbackController.java | 2 +- .../tractusx/irs/component/Tombstone.java | 59 --------- .../registryclient/DefaultConfiguration.java | 2 +- .../EndpointDataForConnectorsService.java | 3 +- ...igitalTwinRegistryServiceWiremockTest.java | 34 +++-- .../EndpointDataForConnectorsServiceTest.java | 21 ++-- 16 files changed, 350 insertions(+), 202 deletions(-) diff --git a/irs-api/src/main/java/org/eclipse/tractusx/irs/aaswrapper/job/delegate/AbstractDelegate.java b/irs-api/src/main/java/org/eclipse/tractusx/irs/aaswrapper/job/delegate/AbstractDelegate.java index e78a394b2..3ce5faaf5 100644 --- a/irs-api/src/main/java/org/eclipse/tractusx/irs/aaswrapper/job/delegate/AbstractDelegate.java +++ b/irs-api/src/main/java/org/eclipse/tractusx/irs/aaswrapper/job/delegate/AbstractDelegate.java @@ -120,7 +120,7 @@ private SubmodelDescriptor getSubmodel(final EdcSubmodelFacade submodelFacade, } throw new EdcClientException( String.format("Called %s connectorEndpoints but did not get any submodels. Connectors: '%s'", - connectorEndpoints.size(), String.join(", ", connectorEndpoints))); + connectorEndpoints.size(), String.join(", ", connectorEndpoints))); // TODO (mfischer) } } diff --git a/irs-api/src/main/java/org/eclipse/tractusx/irs/aaswrapper/job/delegate/DigitalTwinDelegate.java b/irs-api/src/main/java/org/eclipse/tractusx/irs/aaswrapper/job/delegate/DigitalTwinDelegate.java index 55f5fc413..826ceb729 100644 --- a/irs-api/src/main/java/org/eclipse/tractusx/irs/aaswrapper/job/delegate/DigitalTwinDelegate.java +++ b/irs-api/src/main/java/org/eclipse/tractusx/irs/aaswrapper/job/delegate/DigitalTwinDelegate.java @@ -42,6 +42,7 @@ import org.eclipse.tractusx.irs.registryclient.DigitalTwinRegistryKey; import org.eclipse.tractusx.irs.registryclient.DigitalTwinRegistryService; import org.eclipse.tractusx.irs.registryclient.exceptions.RegistryServiceException; +import org.eclipse.tractusx.irs.registryclient.exceptions.ShellNotFoundException; /** * Retrieves AAShell from Digital Twin Registry service and storing it inside {@link ItemContainer}. @@ -72,22 +73,20 @@ public ItemContainer process(final ItemContainer.ItemContainerBuilder itemContai final var dtrKeys = List.of(new DigitalTwinRegistryKey(itemId.getGlobalAssetId(), itemId.getBpn())); final var shells = digitalTwinRegistryService.fetchShells(dtrKeys); final var shell = shells.stream() - // we use findFirst here, because we query only for one - // DigitalTwinRegistryKey here - .map(Either::getOrNull) - .filter(Objects::nonNull) - .findFirst() - .orElseThrow(() -> shellNotFound(shells)); + // we use findFirst here, because we query only for one + // DigitalTwinRegistryKey here + .map(Either::getOrNull) + .filter(Objects::nonNull) + .findFirst() + .orElseThrow(() -> shellNotFound(shells)); itemContainerBuilder.shell( jobData.isAuditContractNegotiation() ? shell : shell.withoutContractAgreementId()); + } catch (final RegistryServiceException | RuntimeException e) { // catching generic exception is intended here, // otherwise Jobs stay in state RUNNING forever - log.info("Shell Endpoint could not be retrieved for Item: {}. Creating Tombstone.", itemId); - itemContainerBuilder.tombstone( - Tombstone.from(itemId.getGlobalAssetId(), null, e, e.getSuppressed(), retryCount, - ProcessStep.DIGITAL_TWIN_REQUEST)); + createShellEndpointCouldNotBeRetrievedTombstone(itemContainerBuilder, itemId, e); } if (expectedDepthOfTreeIsNotReached(jobData.getDepth(), aasTransferProcess.getDepth())) { @@ -98,7 +97,35 @@ public ItemContainer process(final ItemContainer.ItemContainerBuilder itemContai return itemContainerBuilder.build(); } - private Tombstone createNoBpnProvidedTombstone(final JobParameter jobData, final PartChainIdentificationKey itemId) { + private void createShellEndpointCouldNotBeRetrievedTombstone( + final ItemContainer.ItemContainerBuilder itemContainerBuilder, final PartChainIdentificationKey itemId, + final Exception exception) { + + log.info("Shell Endpoint could not be retrieved for Item: {}. Creating Tombstone.", itemId); + + final List rootErrorMessages = Tombstone.getRootErrorMessages(exception.getSuppressed()); + final ProcessingError error = ProcessingError.builder() + .withProcessStep(ProcessStep.DIGITAL_TWIN_REQUEST) + .withRetryCounterAndLastAttemptNow(retryCount) + .withErrorDetail(exception.getMessage()) + .withRootCauses(rootErrorMessages) + .build(); + String endpointURL = null; // TODO (mfischer) test + if (exception instanceof ShellNotFoundException) { + endpointURL = String.join("; ", ((ShellNotFoundException) exception).getCalledEndpoints()); + } + final Tombstone tombstone = Tombstone.builder() + .endpointURL(endpointURL) + .catenaXId(itemId.getGlobalAssetId()) + .processingError(error) + .businessPartnerNumber(itemId.getBpn()) + .build(); + itemContainerBuilder.tombstone(tombstone); + } + + + private Tombstone createNoBpnProvidedTombstone(final JobParameter jobData, + final PartChainIdentificationKey itemId) { log.warn("Could not process item with id {} because no BPN was provided. Creating Tombstone.", itemId.getGlobalAssetId()); diff --git a/irs-api/src/main/java/org/eclipse/tractusx/irs/aaswrapper/job/delegate/RelationshipDelegate.java b/irs-api/src/main/java/org/eclipse/tractusx/irs/aaswrapper/job/delegate/RelationshipDelegate.java index f45dca48e..1516a3c57 100644 --- a/irs-api/src/main/java/org/eclipse/tractusx/irs/aaswrapper/job/delegate/RelationshipDelegate.java +++ b/irs-api/src/main/java/org/eclipse/tractusx/irs/aaswrapper/job/delegate/RelationshipDelegate.java @@ -24,6 +24,7 @@ package org.eclipse.tractusx.irs.aaswrapper.job.delegate; import java.util.List; +import java.util.Map; import lombok.extern.slf4j.Slf4j; import org.apache.commons.lang3.StringUtils; @@ -32,6 +33,7 @@ import org.eclipse.tractusx.irs.component.Bpn; import org.eclipse.tractusx.irs.component.JobParameter; import org.eclipse.tractusx.irs.component.PartChainIdentificationKey; +import org.eclipse.tractusx.irs.component.ProcessingError; import org.eclipse.tractusx.irs.component.Relationship; import org.eclipse.tractusx.irs.component.Tombstone; import org.eclipse.tractusx.irs.component.assetadministrationshell.Endpoint; @@ -41,6 +43,7 @@ import org.eclipse.tractusx.irs.data.JsonParseException; import org.eclipse.tractusx.irs.edc.client.EdcSubmodelFacade; import org.eclipse.tractusx.irs.edc.client.exceptions.EdcClientException; +import org.eclipse.tractusx.irs.edc.client.exceptions.PolicyException; import org.eclipse.tractusx.irs.edc.client.exceptions.UsagePolicyExpiredException; import org.eclipse.tractusx.irs.edc.client.exceptions.UsagePolicyPermissionException; import org.eclipse.tractusx.irs.edc.client.relationships.RelationshipAspect; @@ -94,9 +97,7 @@ private void processEndpoint(final Endpoint endpoint, final RelationshipAspect r if (StringUtils.isBlank(itemId.getBpn())) { log.warn("Could not process item with id {} because no BPN was provided. Creating Tombstone.", itemId.getGlobalAssetId()); - itemContainerBuilder.tombstone( - Tombstone.from(itemId.getGlobalAssetId(), endpoint.getProtocolInformation().getHref(), - "Can't get relationship without a BPN", retryCount, ProcessStep.SUBMODEL_REQUEST)); + itemContainerBuilder.tombstone(createNoBpnProvidedTombstone(endpoint, itemId)); return; } @@ -111,30 +112,77 @@ private void processEndpoint(final Endpoint endpoint, final RelationshipAspect r relationshipAspect.getDirection()); log.info("Processing Relationships with {} items", idsToProcess.size()); - aasTransferProcess.addIdsToProcess(idsToProcess); itemContainerBuilder.relationships(relationships); itemContainerBuilder.bpns(getBpnsFrom(relationships)); + } catch (final UsagePolicyPermissionException | UsagePolicyExpiredException e) { log.info("Encountered usage policy exception: {}. Creating Tombstone.", e.getMessage()); - itemContainerBuilder.tombstone( - Tombstone.from(itemId.getGlobalAssetId(), endpoint.getProtocolInformation().getHref(), e, 0, - ProcessStep.USAGE_POLICY_VALIDATION, e.getBusinessPartnerNumber(), - jsonUtil.asMap(e.getPolicy()))); + final Tombstone tombstone = createPolicyTombstone(endpoint, itemId, e); + itemContainerBuilder.tombstone(tombstone); + } catch (final EdcClientException e) { log.info("Submodel Endpoint could not be retrieved for Endpoint: {}. Creating Tombstone.", endpoint.getProtocolInformation().getHref()); - itemContainerBuilder.tombstone( - Tombstone.from(itemId.getGlobalAssetId(), endpoint.getProtocolInformation().getHref(), e, 0, - ProcessStep.SUBMODEL_REQUEST)); + final Tombstone tombstone = createEdcClientExceptionTombstone(endpoint, itemId, e); + itemContainerBuilder.tombstone(tombstone); + } catch (final JsonParseException e) { log.info("Submodel payload did not match the expected AspectType. Creating Tombstone."); - itemContainerBuilder.tombstone( - Tombstone.from(itemId.getGlobalAssetId(), endpoint.getProtocolInformation().getHref(), e, 0, - ProcessStep.SUBMODEL_REQUEST)); + final Tombstone tombstone = createJsonParseSubmodelPayloadTombstone(endpoint, itemId, e); + itemContainerBuilder.tombstone(tombstone); } } + private Tombstone createNoBpnProvidedTombstone(final Endpoint endpoint, final PartChainIdentificationKey itemId) { + final ProcessingError error = createProcessingError(ProcessStep.SUBMODEL_REQUEST, retryCount, + "Can't get relationship without a BPN"); + return createTombstone(endpoint.getProtocolInformation().getHref(), itemId.getGlobalAssetId(), error, + itemId.getBpn()); + } + + private Tombstone createPolicyTombstone(final Endpoint endpoint, final PartChainIdentificationKey itemId, + final PolicyException exception) { + final Map policy = jsonUtil.asMap(exception.getPolicy()); + final ProcessingError error = createProcessingError(ProcessStep.USAGE_POLICY_VALIDATION, 0, + exception.getMessage()); + return createTombstone(endpoint.getProtocolInformation().getHref(), itemId.getGlobalAssetId(), error, + exception.getBusinessPartnerNumber()).toBuilder().policy(policy).build(); + } + + private Tombstone createEdcClientExceptionTombstone(final Endpoint endpoint, + final PartChainIdentificationKey itemId, final EdcClientException exception) { + final ProcessingError error = createProcessingError(ProcessStep.SUBMODEL_REQUEST, 0, exception.getMessage()); + return createTombstone(endpoint.getProtocolInformation().getHref(), itemId.getGlobalAssetId(), error, + itemId.getBpn()); + } + + private Tombstone createJsonParseSubmodelPayloadTombstone(final Endpoint endpoint, + final PartChainIdentificationKey itemId, final JsonParseException exception) { + final ProcessingError error = createProcessingError(ProcessStep.SUBMODEL_REQUEST, 0, exception.getMessage()); + return createTombstone(endpoint.getProtocolInformation().getHref(), itemId.getGlobalAssetId(), error, + itemId.getBpn()); + } + + private ProcessingError createProcessingError(final ProcessStep processStep, final int retryCount, + final String exception) { + return ProcessingError.builder() + .withProcessStep(processStep) + .withRetryCounterAndLastAttemptNow(retryCount) + .withErrorDetail(exception) + .build(); + } + + private Tombstone createTombstone(final String endpointURL, final String globalAssetId, final ProcessingError error, + final String bpn) { + return Tombstone.builder() + .endpointURL(endpointURL) + .catenaXId(globalAssetId) + .processingError(error) + .businessPartnerNumber(bpn) + .build(); + } + private static List getBpnsFrom(final List relationships) { return relationships.stream() .map(Relationship::getBpn) diff --git a/irs-api/src/main/java/org/eclipse/tractusx/irs/aaswrapper/job/delegate/SubmodelDelegate.java b/irs-api/src/main/java/org/eclipse/tractusx/irs/aaswrapper/job/delegate/SubmodelDelegate.java index ca92b2f79..8a89e63c7 100644 --- a/irs-api/src/main/java/org/eclipse/tractusx/irs/aaswrapper/job/delegate/SubmodelDelegate.java +++ b/irs-api/src/main/java/org/eclipse/tractusx/irs/aaswrapper/job/delegate/SubmodelDelegate.java @@ -27,13 +27,13 @@ import java.util.List; import java.util.Map; -import io.github.resilience4j.retry.RetryRegistry; import lombok.extern.slf4j.Slf4j; import org.apache.commons.lang3.StringUtils; import org.eclipse.tractusx.irs.aaswrapper.job.AASTransferProcess; import org.eclipse.tractusx.irs.aaswrapper.job.ItemContainer; import org.eclipse.tractusx.irs.component.JobParameter; import org.eclipse.tractusx.irs.component.PartChainIdentificationKey; +import org.eclipse.tractusx.irs.component.ProcessingError; import org.eclipse.tractusx.irs.component.Submodel; import org.eclipse.tractusx.irs.component.Tombstone; import org.eclipse.tractusx.irs.component.assetadministrationshell.SubmodelDescriptor; @@ -107,17 +107,21 @@ public ItemContainer process(final ItemContainer.ItemContainerBuilder itemContai private List getSubmodels(final SubmodelDescriptor submodelDescriptor, final ItemContainer.ItemContainerBuilder itemContainerBuilder, final String itemId, final String bpn, final boolean auditContractNegotiation) { + final List submodels = new ArrayList<>(); submodelDescriptor.getEndpoints().forEach(endpoint -> { + final String endpointURL = endpoint.getProtocolInformation().getHref(); if (StringUtils.isBlank(bpn)) { log.warn("Could not process item with id {} because no BPN was provided. Creating Tombstone.", itemId); - itemContainerBuilder.tombstone(Tombstone.from(itemId, endpoint.getProtocolInformation().getHref(), - "Can't get submodel without a BPN", retryCount, ProcessStep.SUBMODEL_REQUEST)); + final ProcessingError error = createProcessingError(ProcessStep.SUBMODEL_REQUEST, retryCount, + "Can't get submodel without a BPN"); + itemContainerBuilder.tombstone(createTombstone(itemId, null, endpointURL, error)); return; } try { + final String jsonSchema = semanticsHubFacade.getModelJsonSchema(submodelDescriptor.getAspectType()); final org.eclipse.tractusx.irs.edc.client.model.SubmodelDescriptor submodel = requestSubmodel( submodelFacade, connectorEndpointsService, endpoint, bpn); @@ -129,34 +133,71 @@ private List getSubmodels(final SubmodelDescriptor submodelDescriptor, if (validationResult.isValid()) { submodels.add(Submodel.from(submodelDescriptor.getId(), submodelDescriptor.getAspectType(), contractAgreementId, jsonUtil.fromString(submodelRawPayload, Map.class))); + } else { - final String errors = String.join(", ", validationResult.getValidationErrors()); - itemContainerBuilder.tombstone(Tombstone.from(itemId, endpoint.getProtocolInformation().getHref(), - new IllegalArgumentException("Submodel payload validation failed. " + errors), 0, - ProcessStep.SCHEMA_VALIDATION)); + final String errorDetail = "Submodel payload validation failed. %s".formatted( + String.join(", ", validationResult.getValidationErrors())); + final ProcessingError error = createProcessingError(ProcessStep.SCHEMA_VALIDATION, 0, errorDetail); + final Tombstone tombstone = createTombstone(itemId, bpn, endpointURL, error); + itemContainerBuilder.tombstone(tombstone); } + } catch (final JsonParseException e) { - itemContainerBuilder.tombstone(Tombstone.from(itemId, endpoint.getProtocolInformation().getHref(), e, - RetryRegistry.ofDefaults().getDefaultConfig().getMaxAttempts(), ProcessStep.SCHEMA_VALIDATION)); log.info("Submodel payload did not match the expected AspectType. Creating Tombstone."); + final ProcessingError error = createProcessingError(ProcessStep.SCHEMA_VALIDATION, retryCount, + e.getMessage()); + final Tombstone tombstone = createTombstone(itemId, bpn, endpointURL, error); + itemContainerBuilder.tombstone(tombstone); + } catch (final SchemaNotFoundException | InvalidSchemaException | RestClientException e) { - itemContainerBuilder.tombstone(Tombstone.from(itemId, endpoint.getProtocolInformation().getHref(), e, 0, - ProcessStep.SCHEMA_REQUEST)); log.info("Cannot load JSON schema for validation. Creating Tombstone."); + final ProcessingError error = createProcessingError(ProcessStep.SCHEMA_REQUEST, 0, e.getMessage()); + itemContainerBuilder.tombstone(createTombstone(itemId, bpn, endpointURL, error)); + } catch (final UsagePolicyPermissionException | UsagePolicyExpiredException e) { log.info("Encountered usage policy permission exception: {}. Creating Tombstone.", e.getMessage()); - itemContainerBuilder.tombstone(Tombstone.from(itemId, endpoint.getProtocolInformation().getHref(), e, 0, - ProcessStep.USAGE_POLICY_VALIDATION, e.getBusinessPartnerNumber(), - jsonUtil.asMap(e.getPolicy()))); + final Map policy = jsonUtil.asMap(e.getPolicy()); + final ProcessingError error = createProcessingError(ProcessStep.USAGE_POLICY_VALIDATION, 0, + e.getMessage()); + final Tombstone tombstone = Tombstone.builder() + .endpointURL(endpointURL) + .catenaXId(itemId) + .processingError(error) + .businessPartnerNumber(e.getBusinessPartnerNumber()) + .policy(policy) + .build(); + itemContainerBuilder.tombstone(tombstone); + } catch (final EdcClientException e) { log.info("Submodel Endpoint could not be retrieved for Item: {}. Creating Tombstone.", itemId); - itemContainerBuilder.tombstone(Tombstone.from(itemId, endpoint.getProtocolInformation().getHref(), e, 0, - ProcessStep.SUBMODEL_REQUEST)); + final ProcessingError error = createProcessingError(ProcessStep.SUBMODEL_REQUEST, 0, e.getMessage()); + final Tombstone tombstone = createTombstone(itemId, bpn, endpointURL, error); + itemContainerBuilder.tombstone(tombstone); } }); + return submodels; } + private Tombstone createTombstone(final String itemId, final String bpn, final String endpointURL, + final ProcessingError error) { + return Tombstone.builder() + .endpointURL(endpointURL) + .catenaXId(itemId) + .processingError(error) + .businessPartnerNumber(bpn) + .build(); + } + + private ProcessingError createProcessingError(final ProcessStep processStep, final int retryCount, + final String errorDetail) { + return ProcessingError.builder() + .withProcessStep(processStep) + .withRetryCounterAndLastAttemptNow(retryCount) + .withErrorDetail(errorDetail) + .build(); + } + @Nullable private String getContractAgreementId(final boolean auditContractNegotiation, final org.eclipse.tractusx.irs.edc.client.model.SubmodelDescriptor submodel) { diff --git a/irs-api/src/main/java/org/eclipse/tractusx/irs/configuration/RegistryConfiguration.java b/irs-api/src/main/java/org/eclipse/tractusx/irs/configuration/RegistryConfiguration.java index 032758c0a..ec10c41d4 100644 --- a/irs-api/src/main/java/org/eclipse/tractusx/irs/configuration/RegistryConfiguration.java +++ b/irs-api/src/main/java/org/eclipse/tractusx/irs/configuration/RegistryConfiguration.java @@ -80,7 +80,7 @@ public DecentralDigitalTwinRegistryService decentralDigitalTwinRegistryService( try { return facade.getEndpointReferencesForRegistryAsset(edcConnectorEndpoint, bpn); } catch (EdcClientException e) { - throw new EdcRetrieverException(e); + throw new EdcRetrieverException.Builder(e).withEdcUrl(edcConnectorEndpoint).withBpn(bpn).build(); } }; diff --git a/irs-api/src/main/java/org/eclipse/tractusx/irs/ess/bpn/validation/IncidentValidation.java b/irs-api/src/main/java/org/eclipse/tractusx/irs/ess/bpn/validation/IncidentValidation.java index a8d30d1af..47a612d8a 100644 --- a/irs-api/src/main/java/org/eclipse/tractusx/irs/ess/bpn/validation/IncidentValidation.java +++ b/irs-api/src/main/java/org/eclipse/tractusx/irs/ess/bpn/validation/IncidentValidation.java @@ -27,6 +27,7 @@ import lombok.extern.slf4j.Slf4j; import org.eclipse.tractusx.irs.component.Jobs; +import org.eclipse.tractusx.irs.component.ProcessingError; import org.eclipse.tractusx.irs.component.Tombstone; import org.eclipse.tractusx.irs.component.enums.AspectType; import org.eclipse.tractusx.irs.component.enums.ProcessStep; @@ -55,12 +56,15 @@ private IncidentValidation() { */ public static InvestigationResult getResult(final BpnInvestigationJob investigationJob, final Jobs job, final UUID completedJobId) { + SupplyChainImpacted partAsPlannedValidity; Jobs completedJob = job; try { partAsPlannedValidity = validatePartAsPlanned(completedJob); } catch (final AspectTypeNotFoundException e) { - completedJob = createTombstone(e, completedJob); + final Tombstone tombstone = createValidationTombstone(e, + completedJob.getJob().getGlobalAssetId().getGlobalAssetId()); + completedJob = completedJob.toBuilder().tombstone(tombstone).build(); partAsPlannedValidity = SupplyChainImpacted.UNKNOWN; } log.info("Local validation of PartAsPlanned Validity was done for job {}. with result {}.", completedJobId, @@ -70,13 +74,17 @@ public static InvestigationResult getResult(final BpnInvestigationJob investigat try { partSiteInformationAsPlannedValidity = validatePartSiteInformationAsPlanned(investigationJob, completedJob); } catch (final ValidationException e) { - completedJob = createTombstone(e, completedJob); + final Tombstone tombstone = createValidationTombstone(e, + completedJob.getJob().getGlobalAssetId().getGlobalAssetId()); + completedJob = completedJob.toBuilder().tombstone(tombstone).build(); partSiteInformationAsPlannedValidity = SupplyChainImpacted.UNKNOWN; } + log.info("Local validation of PartSiteInformationAsPlanned Validity was done for job {}. with result {}.", completedJobId, partSiteInformationAsPlannedValidity); final SupplyChainImpacted supplyChainImpacted = partAsPlannedValidity.or(partSiteInformationAsPlannedValidity); + log.debug("Supply Chain Validity result of {} and {} resulted in {}", partAsPlannedValidity, partSiteInformationAsPlannedValidity, supplyChainImpacted); return new InvestigationResult(completedJob, supplyChainImpacted); @@ -118,10 +126,16 @@ private static String getAspectTypeFromJob(final Jobs job, final AspectType aspe .getPayload()); } - private static Jobs createTombstone(final ValidationException exception, final Jobs completedJob) { + private static Tombstone createValidationTombstone(final ValidationException exception, + final String globalAssetId) { log.warn("Validation failed. {}", exception.getMessage()); - final Tombstone tombstone = Tombstone.from(completedJob.getJob().getGlobalAssetId().getGlobalAssetId(), null, exception, - 0, ProcessStep.ESS_VALIDATION); - return completedJob.toBuilder().tombstone(tombstone).build(); + return Tombstone.builder().catenaXId(globalAssetId).endpointURL(null) // TODO (mfischer) endpointUrl? + .processingError(ProcessingError.builder() + .withErrorDetail(exception.getMessage()) + .withRetryCounterAndLastAttemptNow(0) + .withProcessStep(ProcessStep.ESS_VALIDATION) + .build()) + // TODO (mfischer) .businessPartnerNumber() where to get it from + .build(); } } diff --git a/irs-api/src/main/java/org/eclipse/tractusx/irs/ess/service/InvestigationJobProcessingEventListener.java b/irs-api/src/main/java/org/eclipse/tractusx/irs/ess/service/InvestigationJobProcessingEventListener.java index 17742207c..44779bd45 100644 --- a/irs-api/src/main/java/org/eclipse/tractusx/irs/ess/service/InvestigationJobProcessingEventListener.java +++ b/irs-api/src/main/java/org/eclipse/tractusx/irs/ess/service/InvestigationJobProcessingEventListener.java @@ -203,7 +203,7 @@ private void sendNotifications(final Jobs completedJob, final BpnInvestigationJo investigationJobUpdate.withUnansweredNotifications(Collections.singletonList(new Notification(notificationId, bpn))); } catch (final EdcClientException e) { log.error("Exception during sending EDC notification.", e); - investigationJobUpdate.update(completedJob, SupplyChainImpacted.UNKNOWN); + investigationJobUpdate.update(completedJob, SupplyChainImpacted.UNKNOWN); // TODO (mfischer) ??? } }); }); 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 4f70fb6c7..5ee8b3c6e 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 @@ -53,6 +53,7 @@ import java.time.Duration; import java.util.List; import java.util.Objects; +import java.util.stream.Stream; import com.github.tomakehurst.wiremock.junit5.WireMockTest; import org.awaitility.Awaitility; @@ -91,19 +92,24 @@ @ContextConfiguration(initializers = IrsWireMockIntegrationTest.MinioConfigInitializer.class) @ActiveProfiles("integrationtest") class IrsWireMockIntegrationTest { + public static final String SEMANTIC_HUB_URL = "http://semantic.hub/models"; public static final String EDC_URL = "http://edc.test"; + private static final String ACCESS_KEY = "accessKey"; private static final String SECRET_KEY = "secretKey"; private static final MinioContainer minioContainer = new MinioContainer( new MinioContainer.CredentialsProvider(ACCESS_KEY, SECRET_KEY)).withReuse(true); + @Autowired private IrsItemGraphQueryService irsService; @Autowired private SemanticHubService semanticHubService; + @Autowired private EndpointDataReferenceStorage endpointDataReferenceStorage; + @Autowired private CacheManager cacheManager; @@ -212,6 +218,9 @@ void shouldCreateTombstoneWhenDiscoveryServiceNotAvailable() { assertThat(jobForJobId.getShells()).isEmpty(); assertThat(jobForJobId.getRelationships()).isEmpty(); assertThat(jobForJobId.getTombstones()).hasSize(1); + assertThat(jobForJobId.getTombstones().get(0).getBusinessPartnerNumber()).isEqualTo( + TEST_BPN); // TODO (mfischer) is this correct? + // TODO (mfischer) also check for Endpoint URL } @Test @@ -365,11 +374,15 @@ void shouldCreateDetailedTombstoneForDiscoveryErrors() { assertThat(jobForJobId.getShells()).isEmpty(); assertThat(jobForJobId.getRelationships()).isEmpty(); assertThat(jobForJobId.getSubmodels()).isEmpty(); + assertThat(jobForJobId.getTombstones()).hasSize(1); final Tombstone actualTombstone = jobForJobId.getTombstones().get(0); assertThat(actualTombstone.getProcessingError().getRootCauses()).hasSize(1); assertThat(actualTombstone.getProcessingError().getRootCauses().get(0)).contains( "No EDC Endpoints could be discovered for BPN '%s'".formatted(TEST_BPN)); + assertThat(actualTombstone.getBusinessPartnerNumber()).isEqualTo(TEST_BPN); + assertThat(actualTombstone.getEndpointURL()).describedAs( + "endpoint url empty because it could not be discovered").isEmpty(); } private void successfulRegistryAndDataRequest(final String globalAssetId, final String idShort, final String bpn, 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 bc9a42531..6d8db2d07 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 @@ -57,6 +57,7 @@ import org.eclipse.tractusx.irs.aaswrapper.job.AASTransferProcess; import org.eclipse.tractusx.irs.aaswrapper.job.ItemContainer; +import org.eclipse.tractusx.irs.aaswrapper.job.ItemContainer.ItemContainerBuilder; import org.eclipse.tractusx.irs.component.JobParameter; import org.eclipse.tractusx.irs.component.PartChainIdentificationKey; import org.eclipse.tractusx.irs.component.Quantity; @@ -90,11 +91,11 @@ void shouldFillItemContainerWithRelationshipAndAddChildIdsToProcess() new SubmodelDescriptor("cid", payload)); when(connectorEndpointsService.fetchConnectorEndpoints(any())).thenReturn(List.of("http://localhost")); - final ItemContainer.ItemContainerBuilder itemContainerWithShell = ItemContainer.builder() - .shell(shell("", shellDescriptor( - List.of(submodelDescriptorWithDspEndpoint( - SINGLE_LEVEL_BOM_AS_BUILT_3_0_0, - "address"))))); + final ItemContainerBuilder itemContainerWithShell = ItemContainer.builder() + .shell(shell("", shellDescriptor( + List.of(submodelDescriptorWithDspEndpoint( + SINGLE_LEVEL_BOM_AS_BUILT_3_0_0, + "address"))))); final AASTransferProcess aasTransferProcess = new AASTransferProcess(); // when @@ -118,11 +119,11 @@ void shouldFillItemContainerWithUpwardRelationshipAndAddChildIdsToProcess() new SubmodelDescriptor("cid", payload)); when(connectorEndpointsService.fetchConnectorEndpoints(any())).thenReturn(List.of("http://localhost")); - final ItemContainer.ItemContainerBuilder itemContainerWithShell = ItemContainer.builder() - .shell(shell("", shellDescriptor( - List.of(submodelDescriptorWithDspEndpoint( - SINGLE_LEVEL_USAGE_AS_BUILT_2_0_0, - "address"))))); + final ItemContainerBuilder itemContainerWithShell = ItemContainer.builder() + .shell(shell("", shellDescriptor( + List.of(submodelDescriptorWithDspEndpoint( + SINGLE_LEVEL_USAGE_AS_BUILT_2_0_0, + "address"))))); final AASTransferProcess aasTransferProcess = new AASTransferProcess(); // when @@ -150,11 +151,11 @@ void shouldFillItemContainerWithUpwardAsPlannedRelationshipAndAddChildIdsToProce new SubmodelDescriptor("cid", payload)); when(connectorEndpointsService.fetchConnectorEndpoints(any())).thenReturn(List.of("http://localhost")); - final ItemContainer.ItemContainerBuilder itemContainerWithShell = ItemContainer.builder() - .shell(shell("", shellDescriptor( - List.of(submodelDescriptorWithDspEndpoint( - SINGLE_LEVEL_USAGE_AS_PLANNED_2_0_0, - "address"))))); + final ItemContainerBuilder itemContainerWithShell = ItemContainer.builder() + .shell(shell("", shellDescriptor( + List.of(submodelDescriptorWithDspEndpoint( + SINGLE_LEVEL_USAGE_AS_PLANNED_2_0_0, + "address"))))); final AASTransferProcess aasTransferProcess = new AASTransferProcess(); // when @@ -183,11 +184,10 @@ void shouldFillItemContainerWithSupportedRelationshipAndAddChildIdsToProcess(fin new SubmodelDescriptor("cid", payload)); when(connectorEndpointsService.fetchConnectorEndpoints(any())).thenReturn(List.of("http://localhost")); - final ItemContainer.ItemContainerBuilder itemContainerWithShell = ItemContainer.builder() - .shell(shell("", shellDescriptor( - List.of(submodelDescriptorWithDspEndpoint( - aspectName, - "address"))))); + final ItemContainerBuilder itemContainerWithShell = ItemContainer.builder() + .shell(shell("", shellDescriptor( + List.of(submodelDescriptorWithDspEndpoint( + aspectName, "address"))))); final AASTransferProcess aasTransferProcess = new AASTransferProcess(); // when @@ -233,11 +233,10 @@ void shouldFillItemContainerWithPotentialFutureMinorVersions(final String relati new SubmodelDescriptor("cid", payload)); when(connectorEndpointsService.fetchConnectorEndpoints(any())).thenReturn(List.of("http://localhost")); - final ItemContainer.ItemContainerBuilder itemContainerWithShell = ItemContainer.builder() - .shell(shell("", shellDescriptor( - List.of(submodelDescriptorWithDspEndpoint( - aspectName, - "address"))))); + final ItemContainerBuilder itemContainerWithShell = ItemContainer.builder() + .shell(shell("", shellDescriptor( + List.of(submodelDescriptorWithDspEndpoint( + aspectName, "address"))))); final AASTransferProcess aasTransferProcess = new AASTransferProcess(); // when @@ -282,11 +281,10 @@ void shouldFillItemContainerWithPreviousVersions(final String relationshipFile, new SubmodelDescriptor("cid", payload)); when(connectorEndpointsService.fetchConnectorEndpoints(any())).thenReturn(List.of("http://localhost")); - final ItemContainer.ItemContainerBuilder itemContainerWithShell = ItemContainer.builder() - .shell(shell("", shellDescriptor( - List.of(submodelDescriptorWithDspEndpoint( - aspectName, - "address"))))); + final ItemContainerBuilder itemContainerWithShell = ItemContainer.builder() + .shell(shell("", shellDescriptor( + List.of(submodelDescriptorWithDspEndpoint( + aspectName, "address"))))); final AASTransferProcess aasTransferProcess = new AASTransferProcess(); // when @@ -313,11 +311,11 @@ public static Stream relationshipParametersPreviousVersions() { @Test void shouldPutTombstoneForMissingBpn() { - final ItemContainer.ItemContainerBuilder itemContainerWithShell = ItemContainer.builder() - .shell(shell("", shellDescriptor( - List.of(submodelDescriptorWithDspEndpoint( - SINGLE_LEVEL_BOM_AS_BUILT_3_0_0, - "address"))))); + final ItemContainerBuilder itemContainerWithShell = ItemContainer.builder() + .shell(shell("", shellDescriptor( + List.of(submodelDescriptorWithDspEndpoint( + SINGLE_LEVEL_BOM_AS_BUILT_3_0_0, + "address"))))); // when final ItemContainer result = relationshipDelegate.process(itemContainerWithShell, jobParameter(), new AASTransferProcess(), PartChainIdentificationKey.builder().globalAssetId("testId").build()); @@ -337,19 +335,23 @@ void shouldCatchRestClientExceptionAndPutTombstone() throws EdcClientException { new EdcClientException("Unable to call endpoint")); when(connectorEndpointsService.fetchConnectorEndpoints(any())).thenReturn(List.of("http://localhost")); - final ItemContainer.ItemContainerBuilder itemContainerWithShell = ItemContainer.builder() - .shell(shell("", shellDescriptor( - List.of(submodelDescriptorWithDspEndpoint( - SINGLE_LEVEL_BOM_AS_BUILT_3_0_0, - "address"))))); + final ItemContainerBuilder itemContainerWithShell = ItemContainer.builder() + .shell(shell("", shellDescriptor( + List.of(submodelDescriptorWithDspEndpoint( + SINGLE_LEVEL_BOM_AS_BUILT_3_0_0, + "address"))))); // when + final PartChainIdentificationKey partChainIdentificationKey = createKey(); final ItemContainer result = relationshipDelegate.process(itemContainerWithShell, jobParameter(), - new AASTransferProcess(), createKey()); + new AASTransferProcess(), partChainIdentificationKey); // then assertThat(result).isNotNull(); assertThat(result.getTombstones()).hasSize(1); + assertThat(result.getTombstones().get(0).getBusinessPartnerNumber()).isEqualTo( + partChainIdentificationKey.getBpn()); // TODO (mfischer) is this correct? + assertThat(result.getTombstones().get(0).getEndpointURL()).isEqualTo("address"); assertThat(result.getTombstones().get(0).getCatenaXId()).isEqualTo("itemId"); assertThat(result.getTombstones().get(0).getProcessingError().getProcessStep()).isEqualTo( ProcessStep.SUBMODEL_REQUEST); @@ -361,19 +363,23 @@ void shouldCatchJsonParseExceptionAndPutTombstone() throws EdcClientException { when(submodelFacade.getSubmodelPayload(anyString(), anyString(), anyString(), any())).thenThrow( new EdcClientException(new Exception("Payload did not match expected submodel"))); when(connectorEndpointsService.fetchConnectorEndpoints(any())).thenReturn(List.of("http://localhost")); - final ItemContainer.ItemContainerBuilder itemContainerWithShell = ItemContainer.builder() - .shell(shell("", shellDescriptor( - List.of(submodelDescriptorWithDspEndpoint( - SINGLE_LEVEL_BOM_AS_BUILT_3_0_0, - "address"))))); + final ItemContainerBuilder itemContainerWithShell = ItemContainer.builder() + .shell(shell("", shellDescriptor( + List.of(submodelDescriptorWithDspEndpoint( + SINGLE_LEVEL_BOM_AS_BUILT_3_0_0, + "address"))))); // when + final PartChainIdentificationKey partChainIdentificationKey = createKey(); final ItemContainer result = relationshipDelegate.process(itemContainerWithShell, jobParameter(), - new AASTransferProcess(), createKey()); + new AASTransferProcess(), partChainIdentificationKey); // then assertThat(result).isNotNull(); assertThat(result.getTombstones()).hasSize(1); + assertThat(result.getTombstones().get(0).getBusinessPartnerNumber()).isEqualTo( + partChainIdentificationKey.getBpn()); // TODO (mfischer) is this correct? + assertThat(result.getTombstones().get(0).getEndpointURL()).isEqualTo("address"); assertThat(result.getTombstones().get(0).getCatenaXId()).isEqualTo("itemId"); assertThat(result.getTombstones().get(0).getProcessingError().getProcessStep()).isEqualTo( ProcessStep.SUBMODEL_REQUEST); @@ -383,22 +389,26 @@ void shouldCatchJsonParseExceptionAndPutTombstone() throws EdcClientException { void shouldCatchUsagePolicyExceptionAndPutTombstone() throws EdcClientException { // given final String businessPartnerNumber = "BPNL000000011111"; - final ItemContainer.ItemContainerBuilder itemContainerWithShell = ItemContainer.builder() - .shell(shell("", shellDescriptor( - List.of(submodelDescriptorWithDspEndpoint( - SINGLE_LEVEL_BOM_AS_BUILT_3_0_0, - "address"))))); + final ItemContainerBuilder itemContainerWithShell = ItemContainer.builder() + .shell(shell("", shellDescriptor( + List.of(submodelDescriptorWithDspEndpoint( + SINGLE_LEVEL_BOM_AS_BUILT_3_0_0, + "address"))))); // when when(submodelFacade.getSubmodelPayload(any(), any(), any(), any())).thenThrow( new UsagePolicyPermissionException(List.of(), null, businessPartnerNumber)); when(connectorEndpointsService.fetchConnectorEndpoints(any())).thenReturn(List.of("connector.endpoint.nl")); + final PartChainIdentificationKey partChainIdentificationKey = createKey(); final ItemContainer result = relationshipDelegate.process(itemContainerWithShell, jobParameter(), - new AASTransferProcess(), createKey()); + new AASTransferProcess(), partChainIdentificationKey); // then assertThat(result).isNotNull(); assertThat(result.getTombstones()).hasSize(1); + assertThat(result.getTombstones().get(0).getBusinessPartnerNumber()).isEqualTo( + businessPartnerNumber); // TODO (mfischer) is this correct or should it be the bpn from partChainIdentificationKey? + assertThat(result.getTombstones().get(0).getEndpointURL()).isEqualTo("address"); assertThat(result.getTombstones().get(0).getCatenaXId()).isEqualTo("itemId"); assertThat(result.getTombstones().get(0).getBusinessPartnerNumber()).isEqualTo(businessPartnerNumber); assertThat(result.getTombstones().get(0).getProcessingError().getProcessStep()).isEqualTo( diff --git a/irs-api/src/test/java/org/eclipse/tractusx/irs/component/TombstoneTest.java b/irs-api/src/test/java/org/eclipse/tractusx/irs/component/TombstoneTest.java index c62e5d8b5..830c54837 100644 --- a/irs-api/src/test/java/org/eclipse/tractusx/irs/component/TombstoneTest.java +++ b/irs-api/src/test/java/org/eclipse/tractusx/irs/component/TombstoneTest.java @@ -31,11 +31,10 @@ class TombstoneTest { @Test - void fromTombstoneTest() { + void buildTombstoneTest() { // arrange final String catenaXId = "5e3e9060-ba73-4d5d-a6c8-dfd5123f4d99"; - final IllegalArgumentException illegalArgumentException = new IllegalArgumentException( - "Some funny error occur"); + final IllegalArgumentException exception = new IllegalArgumentException("Some funny error occur"); final String endPointUrl = "http://localhost/dummy/interfaceinformation/urn:uuid:8a61c8db-561e-4db0-84ec-a693fc5ffdf6"; final ProcessingError processingError = ProcessingError.builder() @@ -53,9 +52,17 @@ void fromTombstoneTest() { .processingError(processingError) .build(); - //act - final Tombstone tombstone = Tombstone.from(catenaXId, endPointUrl, illegalArgumentException, - RetryRegistry.ofDefaults().getDefaultConfig().getMaxAttempts(), ProcessStep.SUBMODEL_REQUEST); + // act + final int retryCount = RetryRegistry.ofDefaults().getDefaultConfig().getMaxAttempts(); + final ProcessingError error = ProcessingError.builder() + .withProcessStep(ProcessStep.SUBMODEL_REQUEST) + .withRetryCounterAndLastAttemptNow(retryCount) + .withErrorDetail(exception.getMessage()) + .build(); + final Tombstone tombstone = Tombstone.builder().endpointURL(endPointUrl) + .catenaXId(catenaXId) + .processingError(error) + .build(); // assert assertThat(tombstone).isNotNull(); @@ -79,12 +86,22 @@ void shouldUseSuppressedExceptionWhenPresent() { final Throwable[] suppressed = exception.getSuppressed(); // act - final Tombstone from = Tombstone.from("testId", "testUrl", exception, suppressed, 1, - ProcessStep.DIGITAL_TWIN_REQUEST); + + final ProcessingError error = ProcessingError.builder() + .withProcessStep(ProcessStep.DIGITAL_TWIN_REQUEST) + .withRetryCounterAndLastAttemptNow(1) + .withErrorDetail(exception.getMessage()) + .withRootCauses(Tombstone.getRootErrorMessages(suppressed)) + .build(); + final Tombstone tombstone = Tombstone.builder() + .endpointURL("testUrl") + .catenaXId("testId") + .processingError(error) + .build(); // assert - assertThat(from.getProcessingError().getErrorDetail()).isEqualTo(exception.getMessage()); - assertThat(from.getProcessingError().getRootCauses()).contains("Exception: " + suppressedExceptionMessage); + assertThat(tombstone.getProcessingError().getErrorDetail()).isEqualTo(exception.getMessage()); + assertThat(tombstone.getProcessingError().getRootCauses()).contains("Exception: " + suppressedExceptionMessage); } @Test @@ -103,12 +120,21 @@ void shouldUseDeepSuppressedExceptionWhenPresent() { final Throwable[] suppressed = exception.getSuppressed(); // act - final Tombstone from = Tombstone.from("testId", "testUrl", exception, suppressed, 1, - ProcessStep.DIGITAL_TWIN_REQUEST); + final ProcessingError error = ProcessingError.builder() + .withProcessStep(ProcessStep.DIGITAL_TWIN_REQUEST) + .withRetryCounterAndLastAttemptNow(1) + .withErrorDetail(exception.getMessage()) + .withRootCauses(Tombstone.getRootErrorMessages(suppressed)) + .build(); + final Tombstone tombstone = Tombstone.builder() + .endpointURL("testUrl") + .catenaXId("testId") + .processingError(error) + .build(); // assert - assertThat(from.getProcessingError().getErrorDetail()).isEqualTo(exception.getMessage()); - assertThat(from.getProcessingError().getRootCauses()).contains("Exception: " + suppressedRootCause); + assertThat(tombstone.getProcessingError().getErrorDetail()).isEqualTo(exception.getMessage()); + assertThat(tombstone.getProcessingError().getRootCauses()).contains("Exception: " + suppressedRootCause); } @Test @@ -119,12 +145,22 @@ void shouldUseExceptionMessageWhenSuppressedExceptionNotPresent() { final Throwable[] suppressed = exception.getSuppressed(); // act - final Tombstone from = Tombstone.from("testId", "testUrl", exception, suppressed, 1, - ProcessStep.DIGITAL_TWIN_REQUEST); + + final ProcessingError error = ProcessingError.builder() + .withProcessStep(ProcessStep.DIGITAL_TWIN_REQUEST) + .withRetryCounterAndLastAttemptNow(1) + .withErrorDetail(exception.getMessage()) + .withRootCauses(Tombstone.getRootErrorMessages(suppressed)) + .build(); + final Tombstone tombstone = Tombstone.builder() + .endpointURL("testUrl") + .catenaXId("testId") + .processingError(error) + .build(); // assert - assertThat(from.getProcessingError().getErrorDetail()).isEqualTo(exception.getMessage()); - assertThat(from.getProcessingError().getRootCauses()).isEmpty(); + assertThat(tombstone.getProcessingError().getErrorDetail()).isEqualTo(exception.getMessage()); + assertThat(tombstone.getProcessingError().getRootCauses()).isEmpty(); } private String zonedDateTimeExcerpt(ZonedDateTime dateTime) { diff --git a/irs-edc-client/src/main/java/org/eclipse/tractusx/irs/edc/client/EdcCallbackController.java b/irs-edc-client/src/main/java/org/eclipse/tractusx/irs/edc/client/EdcCallbackController.java index a7571b7ef..2671384e6 100644 --- a/irs-edc-client/src/main/java/org/eclipse/tractusx/irs/edc/client/EdcCallbackController.java +++ b/irs-edc-client/src/main/java/org/eclipse/tractusx/irs/edc/client/EdcCallbackController.java @@ -70,7 +70,7 @@ public void receiveEdcCallback(final @RequestBody String endpointDataReferenceCa final String contractId = endpointDataReference.getContractId(); storeEdr(contractId, endpointDataReference); } catch (EdcClientException e) { - log.error("Could not deserialize Endpoint Data Reference {}", endpointDataReferenceCallback); + log.error("Could not deserialize Endpoint Data Reference {}", endpointDataReferenceCallback); // TODO (mfischer)??? } } diff --git a/irs-models/src/main/java/org/eclipse/tractusx/irs/component/Tombstone.java b/irs-models/src/main/java/org/eclipse/tractusx/irs/component/Tombstone.java index 1d2f48627..7ca62db58 100644 --- a/irs-models/src/main/java/org/eclipse/tractusx/irs/component/Tombstone.java +++ b/irs-models/src/main/java/org/eclipse/tractusx/irs/component/Tombstone.java @@ -23,8 +23,6 @@ ********************************************************************************/ package org.eclipse.tractusx.irs.component; -import java.time.ZoneOffset; -import java.time.ZonedDateTime; import java.util.Arrays; import java.util.List; import java.util.Map; @@ -35,7 +33,6 @@ import lombok.extern.jackson.Jacksonized; import org.apache.commons.lang3.exception.ExceptionUtils; import org.eclipse.tractusx.irs.component.enums.NodeType; -import org.eclipse.tractusx.irs.component.enums.ProcessStep; /** * Tombstone with information about request failure @@ -60,62 +57,6 @@ public class Tombstone { private final ProcessingError processingError; private final Map policy; - @Deprecated // TODO (mfischer) remove this method, use builder instead - public static Tombstone from(final String catenaXId, final String endpointURL, final Exception exception, - final int retryCount, final ProcessStep processStep) { - return from(catenaXId, endpointURL, exception.getMessage(), retryCount, processStep); - } - - @Deprecated // TODO (mfischer) remove this method, use builder instead - public static Tombstone from(final String catenaXId, final String endpointURL, final Exception exception, - final int retryCount, final ProcessStep processStep, final String businessPartnerNumber, - final Map policy) { - - return Tombstone.builder() - .endpointURL(endpointURL) - .catenaXId(catenaXId) - .processingError(ProcessingError.builder() - .withProcessStep(processStep) - .withRetryCounter(retryCount) - .withLastAttempt(ZonedDateTime.now(ZoneOffset.UTC)) - .withErrorDetail(exception.getMessage()) - .build()) - .businessPartnerNumber(businessPartnerNumber) - .policy(policy) - .build(); - } - - @Deprecated // TODO (mfischer) remove this method, use builder instead - public static Tombstone from(final String catenaXId, final String endpointURL, final String errorDetails, - final int retryCount, final ProcessStep processStep) { - - return Tombstone.builder() - .endpointURL(endpointURL) - .catenaXId(catenaXId) - .processingError(ProcessingError.builder() - .withProcessStep(processStep) - .withRetryCounter(retryCount) - .withLastAttempt(ZonedDateTime.now(ZoneOffset.UTC)) - .withErrorDetail(errorDetails) - .build()) - .build(); - } - - @Deprecated // TODO (mfischer) remove this method, use builder instead - public static Tombstone from(final String globalAssetId, final String endpointURL, final Throwable exception, - final Throwable[] suppressed, final int retryCount, final ProcessStep processStep) { - return Tombstone.builder() - .endpointURL(endpointURL) - .catenaXId(globalAssetId) - .processingError(ProcessingError.builder() - .withProcessStep(processStep) - .withRetryCounterAndLastAttemptNow(retryCount) - .withErrorDetail(exception.getMessage()) - .withRootCauses(getRootErrorMessages(suppressed)) - .build()) - .build(); - } - public static List getRootErrorMessages(final Throwable... throwables) { return Arrays.stream(throwables).map(Tombstone::getRootErrorMessages).toList(); } diff --git a/irs-registry-client/src/main/java/org/eclipse/tractusx/irs/registryclient/DefaultConfiguration.java b/irs-registry-client/src/main/java/org/eclipse/tractusx/irs/registryclient/DefaultConfiguration.java index a15eeb722..230c66d7e 100644 --- a/irs-registry-client/src/main/java/org/eclipse/tractusx/irs/registryclient/DefaultConfiguration.java +++ b/irs-registry-client/src/main/java/org/eclipse/tractusx/irs/registryclient/DefaultConfiguration.java @@ -122,7 +122,7 @@ public EndpointDataForConnectorsService endpointDataForConnectorsService(final E try { return facade.getEndpointReferencesForRegistryAsset(edcConnectorEndpoint, bpn); } catch (EdcClientException e) { - throw new EdcRetrieverException(e); + throw new EdcRetrieverException.Builder(e).withEdcUrl(edcConnectorEndpoint).withBpn(bpn).build(); } }; diff --git a/irs-registry-client/src/main/java/org/eclipse/tractusx/irs/registryclient/decentral/EndpointDataForConnectorsService.java b/irs-registry-client/src/main/java/org/eclipse/tractusx/irs/registryclient/decentral/EndpointDataForConnectorsService.java index a71abc4d6..6e6d4b3ed 100644 --- a/irs-registry-client/src/main/java/org/eclipse/tractusx/irs/registryclient/decentral/EndpointDataForConnectorsService.java +++ b/irs-registry-client/src/main/java/org/eclipse/tractusx/irs/registryclient/decentral/EndpointDataForConnectorsService.java @@ -78,7 +78,8 @@ private List> createGetEndpointReferenc return edcSubmodelFacade.getEndpointReferencesForAsset(edcUrl, bpn); } catch (EdcRetrieverException e) { log.warn("Exception occurred when retrieving EndpointDataReference from connector '{}'", edcUrl, e); - return List.of(CompletableFuture.failedFuture(e)); + return List.of(CompletableFuture.failedFuture( + new EdcRetrieverException.Builder(e).withBpn(bpn).withEdcUrl(edcUrl).build())); } finally { watch.stop(); log.info(TOOK_MS, watch.getLastTaskName(), watch.getLastTaskTimeMillis()); diff --git a/irs-registry-client/src/test/java/org/eclipse/tractusx/irs/registryclient/decentral/DecentralDigitalTwinRegistryServiceWiremockTest.java b/irs-registry-client/src/test/java/org/eclipse/tractusx/irs/registryclient/decentral/DecentralDigitalTwinRegistryServiceWiremockTest.java index 915a384f8..f34da4215 100644 --- a/irs-registry-client/src/test/java/org/eclipse/tractusx/irs/registryclient/decentral/DecentralDigitalTwinRegistryServiceWiremockTest.java +++ b/irs-registry-client/src/test/java/org/eclipse/tractusx/irs/registryclient/decentral/DecentralDigitalTwinRegistryServiceWiremockTest.java @@ -113,7 +113,8 @@ void shouldDiscoverEDCAndRequestRegistry() throws RegistryServiceException, EdcR givenThat(getShellDescriptor200()); final var endpointDataReference = endpointDataReference("assetId"); - when(edcEndpointReferenceRetrieverMock.getEndpointReferencesForAsset(any(), any())).thenReturn(List.of(CompletableFuture.completedFuture(endpointDataReference))); + when(edcEndpointReferenceRetrieverMock.getEndpointReferencesForAsset(any(), any())).thenReturn( + List.of(CompletableFuture.completedFuture(endpointDataReference))); // Act final Collection> shells = decentralDigitalTwinRegistryService.fetchShells( @@ -161,7 +162,8 @@ void shouldThrowInCaseOfLookupShellsError() throws EdcRetrieverException { givenThat(postEdcDiscovery200()); final var endpointDataReference = endpointDataReference("assetId"); - when(edcEndpointReferenceRetrieverMock.getEndpointReferencesForAsset(any(), any())).thenReturn(List.of(CompletableFuture.completedFuture(endpointDataReference))); + when(edcEndpointReferenceRetrieverMock.getEndpointReferencesForAsset(any(), any())).thenReturn( + List.of(CompletableFuture.completedFuture(endpointDataReference))); givenThat(getLookupShells404()); final List testId = List.of(new DigitalTwinRegistryKey("testId", TEST_BPN)); @@ -181,7 +183,8 @@ void shouldThrowInCaseOfShellDescriptorsError() throws EdcRetrieverException { givenThat(postEdcDiscovery200()); final var endpointDataReference = endpointDataReference("assetId"); - when(edcEndpointReferenceRetrieverMock.getEndpointReferencesForAsset(any(), any())).thenReturn(List.of(CompletableFuture.completedFuture(endpointDataReference))); + when(edcEndpointReferenceRetrieverMock.getEndpointReferencesForAsset(any(), any())).thenReturn( + List.of(CompletableFuture.completedFuture(endpointDataReference))); givenThat(getLookupShells200()); givenThat(getShellDescriptor404()); @@ -203,7 +206,8 @@ void shouldThrowExceptionOnEmptyShells() throws EdcRetrieverException { givenThat(postEdcDiscovery200()); final var endpointDataReference = endpointDataReference("assetId"); - when(edcEndpointReferenceRetrieverMock.getEndpointReferencesForAsset(any(), any())).thenReturn(List.of(CompletableFuture.completedFuture(endpointDataReference))); + when(edcEndpointReferenceRetrieverMock.getEndpointReferencesForAsset(any(), any())).thenReturn( + List.of(CompletableFuture.completedFuture(endpointDataReference))); givenThat(getLookupShells200Empty()); givenThat(getShellDescriptor404()); @@ -232,7 +236,8 @@ void lookupShellIdentifiers_oneEDC_oneDTR() throws RegistryServiceException, Edc // simulate endpoint data reference final var endpointDataReference = endpointDataReference("assetId"); - when(edcEndpointReferenceRetrieverMock.getEndpointReferencesForAsset(any(), any())).thenReturn(List.of(CompletableFuture.completedFuture(endpointDataReference))); + when(edcEndpointReferenceRetrieverMock.getEndpointReferencesForAsset(any(), any())).thenReturn( + List.of(CompletableFuture.completedFuture(endpointDataReference))); // Act final Collection digitalTwinRegistryKeys = decentralDigitalTwinRegistryService.lookupShellIdentifiers( @@ -262,8 +267,10 @@ void lookupShellIdentifiers_multipleEDCs_oneDTR(String title, // simulate endpoint data reference final var endpointDataReference = endpointDataReference("assetId"); - when(edcEndpointReferenceRetrieverMock.getEndpointReferencesForAsset(eq(edc1Url), any())).thenReturn(List.of(CompletableFuture.completedFuture(endpointDataReference))); - when(edcEndpointReferenceRetrieverMock.getEndpointReferencesForAsset(eq(edc2Url), any())).thenReturn(endpointDataReferenceForAssetFutures); + when(edcEndpointReferenceRetrieverMock.getEndpointReferencesForAsset(eq(edc1Url), any())).thenReturn( + List.of(CompletableFuture.completedFuture(endpointDataReference))); + when(edcEndpointReferenceRetrieverMock.getEndpointReferencesForAsset(eq(edc2Url), any())).thenReturn( + endpointDataReferenceForAssetFutures); // Act final Collection digitalTwinRegistryKeys = decentralDigitalTwinRegistryService.lookupShellIdentifiers( @@ -285,9 +292,10 @@ public Stream provideArguments(final ExtensionContext exten return Stream.of( // failed future Arguments.of("given failed future", List.of(CompletableFuture.failedFuture( - new EdcRetrieverException(new EdcClientException(new RuntimeException("test")))))), - // no result - Arguments.of("given no result", Collections.emptyList())); + new EdcRetrieverException.Builder( + new EdcClientException(new RuntimeException("test"))).build())), + // no result + Arguments.of("given no result", Collections.emptyList()))); } } @@ -305,8 +313,10 @@ void lookupShellIdentifiers_multipleEDCs_multipleDTRs() throws RegistryServiceEx // simulate endpoint data reference final var endpointDataReference1 = endpointDataReference("dtr1-assetId"); final var endpointDataReference2 = endpointDataReference("dtr2-assetId"); - when(edcEndpointReferenceRetrieverMock.getEndpointReferencesForAsset(eq(edc1Url), any())).thenReturn(List.of(CompletableFuture.completedFuture(endpointDataReference1))); - when(edcEndpointReferenceRetrieverMock.getEndpointReferencesForAsset(eq(edc2Url), any())).thenReturn(List.of(CompletableFuture.completedFuture(endpointDataReference2))); + when(edcEndpointReferenceRetrieverMock.getEndpointReferencesForAsset(eq(edc1Url), any())).thenReturn( + List.of(CompletableFuture.completedFuture(endpointDataReference1))); + when(edcEndpointReferenceRetrieverMock.getEndpointReferencesForAsset(eq(edc2Url), any())).thenReturn( + List.of(CompletableFuture.completedFuture(endpointDataReference2))); // Act & Assert final Collection digitalTwinRegistryKeys = decentralDigitalTwinRegistryService.lookupShellIdentifiers( diff --git a/irs-registry-client/src/test/java/org/eclipse/tractusx/irs/registryclient/decentral/EndpointDataForConnectorsServiceTest.java b/irs-registry-client/src/test/java/org/eclipse/tractusx/irs/registryclient/decentral/EndpointDataForConnectorsServiceTest.java index 30163c000..865fc4374 100644 --- a/irs-registry-client/src/test/java/org/eclipse/tractusx/irs/registryclient/decentral/EndpointDataForConnectorsServiceTest.java +++ b/irs-registry-client/src/test/java/org/eclipse/tractusx/irs/registryclient/decentral/EndpointDataForConnectorsServiceTest.java @@ -24,6 +24,7 @@ package org.eclipse.tractusx.irs.registryclient.decentral; import static org.assertj.core.api.Assertions.assertThat; +import static org.assertj.core.api.AssertionsForClassTypes.tuple; import static org.mockito.ArgumentMatchers.anyString; import static org.mockito.ArgumentMatchers.eq; import static org.mockito.Mockito.mock; @@ -59,7 +60,8 @@ class EndpointDataForConnectorsServiceTest { .build(); private static final EndpointDataReference CONNECTION_TWO_DATA_REF = // - EndpointDataReference.Builder.newInstance().endpoint(CONNECTION_TWO_ADDRESS) + EndpointDataReference.Builder.newInstance() + .endpoint(CONNECTION_TWO_ADDRESS) .contractId(CONNECTION_TWO_CONTRACT_ID) .id("test1") .authKey(HttpHeaders.AUTHORIZATION) @@ -96,7 +98,7 @@ void shouldReturnExpectedEndpointDataReferenceFromSecondConnectionEndpoint() thr // a first endpoint failing (1) when(edcSubmodelFacade.getEndpointReferencesForAsset(CONNECTION_ONE_ADDRESS, BPN)).thenThrow( - new EdcRetrieverException(new EdcClientException("EdcClientException"))); + new EdcRetrieverException.Builder(new EdcClientException("EdcClientException")).build()); // and a second endpoint returning successfully (2) when(edcSubmodelFacade.getEndpointReferencesForAsset(CONNECTION_TWO_ADDRESS, BPN)).thenReturn( @@ -135,12 +137,10 @@ void shouldThrowExceptionWhenConnectorEndpointsNotReachable() throws EdcRetrieve // GIVEN when(edcSubmodelFacade.getEndpointReferencesForAsset(anyString(), eq(BPN))).thenThrow( - new EdcRetrieverException(new EdcClientException("EdcClientException"))); + new EdcRetrieverException.Builder(new EdcClientException("EdcClientException")).build()); // WHEN - final var exceptions = new ArrayList<>(); - - // THEN + final List exceptions = new ArrayList<>(); final List connectorEndpoints = List.of(CONNECTION_ONE_ADDRESS, CONNECTION_TWO_ADDRESS); sut.createFindEndpointDataForConnectorsFutures(connectorEndpoints, BPN) // .forEach(future -> { @@ -151,7 +151,14 @@ void shouldThrowExceptionWhenConnectorEndpointsNotReachable() throws EdcRetrieve } }); - assertThat(exceptions).hasSize(connectorEndpoints.size()); + // THEN + assertThat(exceptions).hasSize(connectorEndpoints.size()) + .extracting(Exception::getCause) + .allMatch(exception -> exception instanceof EdcRetrieverException) + .extracting("bpn", "edcUrl") + .containsExactlyInAnyOrder(tuple(BPN, CONNECTION_ONE_ADDRESS), + tuple(BPN, CONNECTION_TWO_ADDRESS)); + } } From dd74856fb59fc964d29008350aaf4a265525853b Mon Sep 17 00:00:00 2001 From: Matthias Fischer Date: Thu, 1 Aug 2024 12:35:40 +0200 Subject: [PATCH 07/27] chore(quality): [#841] formatting --- .../tractusx/irs/aaswrapper/job/delegate/AbstractDelegate.java | 2 ++ 1 file changed, 2 insertions(+) diff --git a/irs-api/src/main/java/org/eclipse/tractusx/irs/aaswrapper/job/delegate/AbstractDelegate.java b/irs-api/src/main/java/org/eclipse/tractusx/irs/aaswrapper/job/delegate/AbstractDelegate.java index 3ce5faaf5..d29b64494 100644 --- a/irs-api/src/main/java/org/eclipse/tractusx/irs/aaswrapper/job/delegate/AbstractDelegate.java +++ b/irs-api/src/main/java/org/eclipse/tractusx/irs/aaswrapper/job/delegate/AbstractDelegate.java @@ -109,6 +109,7 @@ protected SubmodelDescriptor requestSubmodel(final EdcSubmodelFacade submodelFac private SubmodelDescriptor getSubmodel(final EdcSubmodelFacade submodelFacade, final Endpoint digitalTwinRegistryEndpoint, final List connectorEndpoints, final String bpn) throws EdcClientException { + for (final String connectorEndpoint : connectorEndpoints) { try { return submodelFacade.getSubmodelPayload(connectorEndpoint, @@ -118,6 +119,7 @@ private SubmodelDescriptor getSubmodel(final EdcSubmodelFacade submodelFacade, log.info("EdcClientException while accessing digitalTwinRegistryEndpoint '{}'", connectorEndpoint, e); } } + throw new EdcClientException( String.format("Called %s connectorEndpoints but did not get any submodels. Connectors: '%s'", connectorEndpoints.size(), String.join(", ", connectorEndpoints))); // TODO (mfischer) From 9ea3551c1949b37512339efa15c21068da160b27 Mon Sep 17 00:00:00 2001 From: Matthias Fischer Date: Thu, 1 Aug 2024 12:36:19 +0200 Subject: [PATCH 08/27] fix(exception-handling): [#841] make constructor private (builder) --- .../irs/registryclient/decentral/EdcRetrieverException.java | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/irs-registry-client/src/main/java/org/eclipse/tractusx/irs/registryclient/decentral/EdcRetrieverException.java b/irs-registry-client/src/main/java/org/eclipse/tractusx/irs/registryclient/decentral/EdcRetrieverException.java index 94273d1a1..3e9a8da52 100644 --- a/irs-registry-client/src/main/java/org/eclipse/tractusx/irs/registryclient/decentral/EdcRetrieverException.java +++ b/irs-registry-client/src/main/java/org/eclipse/tractusx/irs/registryclient/decentral/EdcRetrieverException.java @@ -38,8 +38,7 @@ public class EdcRetrieverException extends Exception { private String edcUrl; - @Deprecated // TODO (mfischer) remove later, use the builder - public EdcRetrieverException(final Throwable cause) { + private EdcRetrieverException(final Throwable cause) { super(cause); } From 2fe49e920807aa46717bf5dd596cb8c122c8b35a Mon Sep 17 00:00:00 2001 From: Matthias Fischer Date: Thu, 1 Aug 2024 12:37:02 +0200 Subject: [PATCH 09/27] fix(exception-handling): [#841] add bpn to validation tombstone --- .../bpn/validation/IncidentValidation.java | 35 +++++++++++++------ 1 file changed, 24 insertions(+), 11 deletions(-) diff --git a/irs-api/src/main/java/org/eclipse/tractusx/irs/ess/bpn/validation/IncidentValidation.java b/irs-api/src/main/java/org/eclipse/tractusx/irs/ess/bpn/validation/IncidentValidation.java index 47a612d8a..9842f7b7f 100644 --- a/irs-api/src/main/java/org/eclipse/tractusx/irs/ess/bpn/validation/IncidentValidation.java +++ b/irs-api/src/main/java/org/eclipse/tractusx/irs/ess/bpn/validation/IncidentValidation.java @@ -23,12 +23,16 @@ ********************************************************************************/ package org.eclipse.tractusx.irs.ess.bpn.validation; +import java.util.Optional; import java.util.UUID; import lombok.extern.slf4j.Slf4j; +import org.eclipse.tractusx.irs.component.Job; +import org.eclipse.tractusx.irs.component.JobParameter; import org.eclipse.tractusx.irs.component.Jobs; import org.eclipse.tractusx.irs.component.ProcessingError; import org.eclipse.tractusx.irs.component.Tombstone; +import org.eclipse.tractusx.irs.component.Tombstone.TombstoneBuilder; import org.eclipse.tractusx.irs.component.enums.AspectType; import org.eclipse.tractusx.irs.component.enums.ProcessStep; import org.eclipse.tractusx.irs.component.partasplanned.PartAsPlanned; @@ -62,8 +66,7 @@ public static InvestigationResult getResult(final BpnInvestigationJob investigat try { partAsPlannedValidity = validatePartAsPlanned(completedJob); } catch (final AspectTypeNotFoundException e) { - final Tombstone tombstone = createValidationTombstone(e, - completedJob.getJob().getGlobalAssetId().getGlobalAssetId()); + final Tombstone tombstone = createValidationTombstone(e, completedJob); completedJob = completedJob.toBuilder().tombstone(tombstone).build(); partAsPlannedValidity = SupplyChainImpacted.UNKNOWN; } @@ -74,8 +77,7 @@ public static InvestigationResult getResult(final BpnInvestigationJob investigat try { partSiteInformationAsPlannedValidity = validatePartSiteInformationAsPlanned(investigationJob, completedJob); } catch (final ValidationException e) { - final Tombstone tombstone = createValidationTombstone(e, - completedJob.getJob().getGlobalAssetId().getGlobalAssetId()); + final Tombstone tombstone = createValidationTombstone(e, completedJob); completedJob = completedJob.toBuilder().tombstone(tombstone).build(); partSiteInformationAsPlannedValidity = SupplyChainImpacted.UNKNOWN; } @@ -126,16 +128,27 @@ private static String getAspectTypeFromJob(final Jobs job, final AspectType aspe .getPayload()); } - private static Tombstone createValidationTombstone(final ValidationException exception, - final String globalAssetId) { + private static Tombstone createValidationTombstone(final ValidationException exception, final Jobs completedJob) { log.warn("Validation failed. {}", exception.getMessage()); - return Tombstone.builder().catenaXId(globalAssetId).endpointURL(null) // TODO (mfischer) endpointUrl? - .processingError(ProcessingError.builder() + + final TombstoneBuilder tombstoneBuilder = Tombstone.builder(); + + tombstoneBuilder.catenaXId(completedJob.getJob().getGlobalAssetId().getGlobalAssetId()); + + // null because failure is before endpoint url is known + tombstoneBuilder.endpointURL(null); + + tombstoneBuilder.processingError(ProcessingError.builder() .withErrorDetail(exception.getMessage()) .withRetryCounterAndLastAttemptNow(0) .withProcessStep(ProcessStep.ESS_VALIDATION) - .build()) - // TODO (mfischer) .businessPartnerNumber() where to get it from - .build(); + .build()); + Optional.of(completedJob) + .map(Jobs::getJob) + .map(Job::getParameter) + .map(JobParameter::getBpn) + .ifPresent(tombstoneBuilder::businessPartnerNumber); + + return tombstoneBuilder.build(); } } From 7778cc460133fe670379db111ef75c0950658fef Mon Sep 17 00:00:00 2001 From: Matthias Fischer Date: Thu, 1 Aug 2024 12:49:48 +0200 Subject: [PATCH 10/27] fix(exception-handling): [#841] add assertions, improve test readability --- .../irs/IrsWireMockIntegrationTest.java | 65 +++++++++---- .../delegate/RelationshipDelegateTest.java | 96 ++++++++++++------- 2 files changed, 109 insertions(+), 52 deletions(-) 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 5ee8b3c6e..b425bc879 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 @@ -32,6 +32,7 @@ import static org.eclipse.tractusx.irs.WiremockSupport.randomUUID; import static org.eclipse.tractusx.irs.component.enums.AspectType.AspectTypesConstants.BATCH; import static org.eclipse.tractusx.irs.component.enums.AspectType.AspectTypesConstants.SINGLE_LEVEL_BOM_AS_BUILT; +import static org.eclipse.tractusx.irs.testing.wiremock.DiscoveryServiceWiremockSupport.CONTROLPLANE_PUBLIC_URL; import static org.eclipse.tractusx.irs.testing.wiremock.DiscoveryServiceWiremockSupport.DISCOVERY_FINDER_PATH; import static org.eclipse.tractusx.irs.testing.wiremock.DiscoveryServiceWiremockSupport.DISCOVERY_FINDER_URL; import static org.eclipse.tractusx.irs.testing.wiremock.DiscoveryServiceWiremockSupport.EDC_DISCOVERY_PATH; @@ -53,7 +54,6 @@ import java.time.Duration; import java.util.List; import java.util.Objects; -import java.util.stream.Stream; import com.github.tomakehurst.wiremock.junit5.WireMockTest; import org.awaitility.Awaitility; @@ -215,12 +215,15 @@ void shouldCreateTombstoneWhenDiscoveryServiceNotAvailable() { verify(0, postRequestedFor(urlPathEqualTo(EDC_DISCOVERY_PATH))); assertThat(jobForJobId.getJob().getState()).isEqualTo(JobState.COMPLETED); + assertThat(jobForJobId.getShells()).isEmpty(); assertThat(jobForJobId.getRelationships()).isEmpty(); - assertThat(jobForJobId.getTombstones()).hasSize(1); - assertThat(jobForJobId.getTombstones().get(0).getBusinessPartnerNumber()).isEqualTo( - TEST_BPN); // TODO (mfischer) is this correct? - // TODO (mfischer) also check for Endpoint URL + + final List tombstones = jobForJobId.getTombstones(); + assertThat(tombstones).hasSize(1); + assertThat(tombstones.get(0).getBusinessPartnerNumber()).isEqualTo(TEST_BPN); + assertThat(tombstones.get(0).getEndpointURL()).describedAs( + "Endpoint URL should be empty because discovery not successful").isEmpty(); } @Test @@ -243,9 +246,15 @@ void shouldCreateTombstoneWhenEdcDiscoveryIsEmpty() { WiremockSupport.verifyDiscoveryCalls(1); assertThat(jobForJobId.getJob().getState()).isEqualTo(JobState.COMPLETED); + assertThat(jobForJobId.getShells()).isEmpty(); assertThat(jobForJobId.getRelationships()).isEmpty(); - assertThat(jobForJobId.getTombstones()).hasSize(1); + + final List tombstones = jobForJobId.getTombstones(); + assertThat(tombstones).hasSize(1); + assertThat(tombstones.get(0).getBusinessPartnerNumber()).isEqualTo(TEST_BPN); + assertThat(tombstones.get(0).getEndpointURL()).describedAs( + "Endpoint URL should be empty because discovery not successful").isEmpty(); } @Test @@ -287,7 +296,7 @@ void shouldStartRecursiveProcesses() { } @Test - void shouldCreateDetailedTombstoneForMissmatchPolicy() { + void shouldCreateDetailedTombstoneForMismatchPolicy() { // Arrange final String globalAssetId = "urn:uuid:334cce52-1f52-4bc9-9dd1-410bbe497bbc"; @@ -308,14 +317,22 @@ void shouldCreateDetailedTombstoneForMissmatchPolicy() { final Jobs jobForJobId = irsService.getJobForJobId(jobHandle.getId(), false); assertThat(jobForJobId.getJob().getState()).isEqualTo(JobState.COMPLETED); + assertThat(jobForJobId.getShells()).isEmpty(); assertThat(jobForJobId.getRelationships()).isEmpty(); assertThat(jobForJobId.getSubmodels()).isEmpty(); + assertThat(jobForJobId.getTombstones()).hasSize(1); + final Tombstone actualTombstone = jobForJobId.getTombstones().get(0); - assertThat(actualTombstone.getProcessingError().getRootCauses()).hasSize(1); - assertThat(actualTombstone.getProcessingError().getRootCauses().get(0)).contains( - "UsagePolicyPermissionException: Policies [default-policy] did not match with policy from BPNL00000000TEST."); + assertThat(actualTombstone.getBusinessPartnerNumber()).isEqualTo(TEST_BPN); + assertThat(actualTombstone.getEndpointURL()).isEqualTo(CONTROLPLANE_PUBLIC_URL); + + final List rootCauses = actualTombstone.getProcessingError().getRootCauses(); + assertThat(rootCauses).hasSize(1); + assertThat(rootCauses.get(0)).contains( + "UsagePolicyPermissionException: Policies [default-policy] did not match with policy from %s.".formatted( + TEST_BPN)); } @Test @@ -340,13 +357,21 @@ void shouldCreateDetailedTombstoneForEdcErrors() { final Jobs jobForJobId = irsService.getJobForJobId(jobHandle.getId(), false); assertThat(jobForJobId.getJob().getState()).isEqualTo(JobState.COMPLETED); + assertThat(jobForJobId.getShells()).isEmpty(); assertThat(jobForJobId.getRelationships()).isEmpty(); assertThat(jobForJobId.getSubmodels()).isEmpty(); - assertThat(jobForJobId.getTombstones()).hasSize(1); - final Tombstone actualTombstone = jobForJobId.getTombstones().get(0); - assertThat(actualTombstone.getProcessingError().getRootCauses()).hasSize(1); - assertThat(actualTombstone.getProcessingError().getRootCauses().get(0)).contains("502 Bad Gateway"); + + final List tombstones = jobForJobId.getTombstones(); + assertThat(tombstones).hasSize(1); + + final Tombstone actualTombstone = tombstones.get(0); + assertThat(actualTombstone.getBusinessPartnerNumber()).isEqualTo(TEST_BPN); + assertThat(actualTombstone.getEndpointURL()).isEqualTo(CONTROLPLANE_PUBLIC_URL); + + final List rootCauses = actualTombstone.getProcessingError().getRootCauses(); + assertThat(rootCauses).hasSize(1); + assertThat(rootCauses.get(0)).contains("502 Bad Gateway"); } @Test @@ -371,18 +396,22 @@ void shouldCreateDetailedTombstoneForDiscoveryErrors() { final Jobs jobForJobId = irsService.getJobForJobId(jobHandle.getId(), false); assertThat(jobForJobId.getJob().getState()).isEqualTo(JobState.COMPLETED); + assertThat(jobForJobId.getShells()).isEmpty(); assertThat(jobForJobId.getRelationships()).isEmpty(); assertThat(jobForJobId.getSubmodels()).isEmpty(); assertThat(jobForJobId.getTombstones()).hasSize(1); final Tombstone actualTombstone = jobForJobId.getTombstones().get(0); - assertThat(actualTombstone.getProcessingError().getRootCauses()).hasSize(1); - assertThat(actualTombstone.getProcessingError().getRootCauses().get(0)).contains( - "No EDC Endpoints could be discovered for BPN '%s'".formatted(TEST_BPN)); + assertThat(actualTombstone.getBusinessPartnerNumber()).isEqualTo(TEST_BPN); assertThat(actualTombstone.getEndpointURL()).describedAs( - "endpoint url empty because it could not be discovered").isEmpty(); + "Endpoint url empty because it could not be discovered").isEmpty(); + + final List rootCauses = actualTombstone.getProcessingError().getRootCauses(); + assertThat(rootCauses).hasSize(1); + assertThat(rootCauses.get(0)).contains( + "No EDC Endpoints could be discovered for BPN '%s'".formatted(TEST_BPN)); } private void successfulRegistryAndDataRequest(final String globalAssetId, final String idShort, final String bpn, 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 6d8db2d07..2e5eb5b33 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 @@ -61,6 +61,7 @@ import org.eclipse.tractusx.irs.component.JobParameter; import org.eclipse.tractusx.irs.component.PartChainIdentificationKey; import org.eclipse.tractusx.irs.component.Quantity; +import org.eclipse.tractusx.irs.component.Tombstone; import org.eclipse.tractusx.irs.component.enums.ProcessStep; import org.eclipse.tractusx.irs.edc.client.EdcSubmodelFacade; import org.eclipse.tractusx.irs.edc.client.exceptions.EdcClientException; @@ -132,12 +133,15 @@ void shouldFillItemContainerWithUpwardRelationshipAndAddChildIdsToProcess() // then assertThat(result).isNotNull(); + final Quantity quantity = result.getRelationships().get(0).getLinkedItem().getQuantity(); assertThat(quantity.getQuantityNumber()).isEqualTo(20.0); assertThat(quantity.getMeasurementUnit().getLexicalValue()).isEqualTo("unit:piece"); - assertThat(aasTransferProcess.getIdsToProcess()).isNotEmpty(); - assertThat(aasTransferProcess.getIdsToProcess().get(0).getGlobalAssetId()).isNotEmpty(); - assertThat(aasTransferProcess.getIdsToProcess().get(0).getBpn()).isNotEmpty(); + + final List idsToProcess = aasTransferProcess.getIdsToProcess(); + assertThat(idsToProcess).isNotEmpty(); + assertThat(idsToProcess.get(0).getGlobalAssetId()).isNotEmpty(); + assertThat(idsToProcess.get(0).getBpn()).isNotEmpty(); } @Test @@ -164,13 +168,15 @@ void shouldFillItemContainerWithUpwardAsPlannedRelationshipAndAddChildIdsToProce // then assertThat(result).isNotNull(); + final Quantity quantity = result.getRelationships().get(0).getLinkedItem().getQuantity(); assertThat(quantity.getQuantityNumber()).isEqualTo(20.0); assertThat(quantity.getMeasurementUnit().getLexicalValue()).isEqualTo("unit:piece"); - assertThat(aasTransferProcess.getIdsToProcess()).isNotEmpty(); - assertThat(aasTransferProcess.getIdsToProcess().get(0).getGlobalAssetId()).isEqualTo( - "urn:uuid:56319907-28dc-440e-afcc-72d67ad343e7"); - assertThat(aasTransferProcess.getIdsToProcess().get(0).getBpn()).isEqualTo("BPNL50096894aNXY"); + + final List idsToProcess = aasTransferProcess.getIdsToProcess(); + assertThat(idsToProcess).isNotEmpty(); + assertThat(idsToProcess.get(0).getGlobalAssetId()).isEqualTo("urn:uuid:56319907-28dc-440e-afcc-72d67ad343e7"); + assertThat(idsToProcess.get(0).getBpn()).isEqualTo("BPNL50096894aNXY"); } @ParameterizedTest @@ -197,12 +203,15 @@ void shouldFillItemContainerWithSupportedRelationshipAndAddChildIdsToProcess(fin // then assertThat(result).isNotNull(); assertThat(result.getRelationships()).hasSize(1); + final Quantity quantity = result.getRelationships().get(0).getLinkedItem().getQuantity(); assertThat(quantity.getQuantityNumber()).isEqualTo(20.0); assertThat(quantity.getMeasurementUnit().getLexicalValue()).isEqualTo("unit:piece"); - assertThat(aasTransferProcess.getIdsToProcess()).isNotEmpty(); - assertThat(aasTransferProcess.getIdsToProcess().get(0).getGlobalAssetId()).isNotEmpty(); - assertThat(aasTransferProcess.getIdsToProcess().get(0).getBpn()).isNotEmpty(); + + final List idsToProcess = aasTransferProcess.getIdsToProcess(); + assertThat(idsToProcess).isNotEmpty(); + assertThat(idsToProcess.get(0).getGlobalAssetId()).isNotEmpty(); + assertThat(idsToProcess.get(0).getBpn()).isNotEmpty(); } private static Stream relationshipParameters() { @@ -246,12 +255,15 @@ void shouldFillItemContainerWithPotentialFutureMinorVersions(final String relati // then assertThat(result).isNotNull(); assertThat(result.getRelationships()).hasSize(1); + final Quantity quantity = result.getRelationships().get(0).getLinkedItem().getQuantity(); assertThat(quantity.getQuantityNumber()).isEqualTo(20.0); assertThat(quantity.getMeasurementUnit().getLexicalValue()).isEqualTo("unit:piece"); - assertThat(aasTransferProcess.getIdsToProcess()).isNotEmpty(); - assertThat(aasTransferProcess.getIdsToProcess().get(0).getGlobalAssetId()).isNotEmpty(); - assertThat(aasTransferProcess.getIdsToProcess().get(0).getBpn()).isNotEmpty(); + + final List idsToProcess = aasTransferProcess.getIdsToProcess(); + assertThat(idsToProcess).isNotEmpty(); + assertThat(idsToProcess.get(0).getGlobalAssetId()).isNotEmpty(); + assertThat(idsToProcess.get(0).getBpn()).isNotEmpty(); } public static Stream relationshipParametersFutureVersions() { @@ -294,12 +306,15 @@ void shouldFillItemContainerWithPreviousVersions(final String relationshipFile, // then assertThat(result).isNotNull(); assertThat(result.getRelationships()).hasSize(1); + final Quantity quantity = result.getRelationships().get(0).getLinkedItem().getQuantity(); assertThat(quantity.getQuantityNumber()).isEqualTo(2.5); assertThat(quantity.getMeasurementUnit().getLexicalValue()).isEqualTo("unit:litre"); - assertThat(aasTransferProcess.getIdsToProcess()).isNotEmpty(); - assertThat(aasTransferProcess.getIdsToProcess().get(0).getGlobalAssetId()).isNotEmpty(); - assertThat(aasTransferProcess.getIdsToProcess().get(0).getBpn()).isNotEmpty(); + + final List idsToProcess = aasTransferProcess.getIdsToProcess(); + assertThat(idsToProcess).isNotEmpty(); + assertThat(idsToProcess.get(0).getGlobalAssetId()).isNotEmpty(); + assertThat(idsToProcess.get(0).getBpn()).isNotEmpty(); } public static Stream relationshipParametersPreviousVersions() { @@ -322,10 +337,15 @@ void shouldPutTombstoneForMissingBpn() { // then assertThat(result).isNotNull(); - assertThat(result.getTombstones()).hasSize(1); - assertThat(result.getTombstones().get(0).getCatenaXId()).isEqualTo("testId"); - assertThat(result.getTombstones().get(0).getProcessingError().getProcessStep()).isEqualTo( - ProcessStep.SUBMODEL_REQUEST); + + final List tombstones = result.getTombstones(); + assertThat(tombstones).hasSize(1); + + assertThat(tombstones.get(0).getEndpointURL()).isEqualTo("address"); + assertThat(tombstones.get(0).getBusinessPartnerNumber()).isNull(); + + assertThat(tombstones.get(0).getCatenaXId()).isEqualTo("testId"); + assertThat(tombstones.get(0).getProcessingError().getProcessStep()).isEqualTo(ProcessStep.SUBMODEL_REQUEST); } @Test @@ -349,12 +369,14 @@ void shouldCatchRestClientExceptionAndPutTombstone() throws EdcClientException { // then assertThat(result).isNotNull(); assertThat(result.getTombstones()).hasSize(1); - assertThat(result.getTombstones().get(0).getBusinessPartnerNumber()).isEqualTo( - partChainIdentificationKey.getBpn()); // TODO (mfischer) is this correct? - assertThat(result.getTombstones().get(0).getEndpointURL()).isEqualTo("address"); - assertThat(result.getTombstones().get(0).getCatenaXId()).isEqualTo("itemId"); - assertThat(result.getTombstones().get(0).getProcessingError().getProcessStep()).isEqualTo( - ProcessStep.SUBMODEL_REQUEST); + + final Tombstone tombstone = result.getTombstones().get(0); + + assertThat(tombstone.getBusinessPartnerNumber()).isEqualTo(partChainIdentificationKey.getBpn()); + assertThat(tombstone.getEndpointURL()).isEqualTo("address"); + + assertThat(tombstone.getCatenaXId()).isEqualTo("itemId"); + assertThat(tombstone.getProcessingError().getProcessStep()).isEqualTo(ProcessStep.SUBMODEL_REQUEST); } @Test @@ -377,9 +399,11 @@ void shouldCatchJsonParseExceptionAndPutTombstone() throws EdcClientException { // then assertThat(result).isNotNull(); assertThat(result.getTombstones()).hasSize(1); + assertThat(result.getTombstones().get(0).getBusinessPartnerNumber()).isEqualTo( - partChainIdentificationKey.getBpn()); // TODO (mfischer) is this correct? + partChainIdentificationKey.getBpn()); assertThat(result.getTombstones().get(0).getEndpointURL()).isEqualTo("address"); + assertThat(result.getTombstones().get(0).getCatenaXId()).isEqualTo("itemId"); assertThat(result.getTombstones().get(0).getProcessingError().getProcessStep()).isEqualTo( ProcessStep.SUBMODEL_REQUEST); @@ -405,14 +429,18 @@ void shouldCatchUsagePolicyExceptionAndPutTombstone() throws EdcClientException // then assertThat(result).isNotNull(); - assertThat(result.getTombstones()).hasSize(1); - assertThat(result.getTombstones().get(0).getBusinessPartnerNumber()).isEqualTo( + + final List tombstones = result.getTombstones(); + assertThat(tombstones).hasSize(1); + + final Tombstone tombstone = tombstones.get(0); + assertThat(tombstone.getBusinessPartnerNumber()).isEqualTo( businessPartnerNumber); // TODO (mfischer) is this correct or should it be the bpn from partChainIdentificationKey? - assertThat(result.getTombstones().get(0).getEndpointURL()).isEqualTo("address"); - assertThat(result.getTombstones().get(0).getCatenaXId()).isEqualTo("itemId"); - assertThat(result.getTombstones().get(0).getBusinessPartnerNumber()).isEqualTo(businessPartnerNumber); - assertThat(result.getTombstones().get(0).getProcessingError().getProcessStep()).isEqualTo( - ProcessStep.USAGE_POLICY_VALIDATION); + assertThat(tombstone.getEndpointURL()).isEqualTo("address"); + + assertThat(tombstone.getCatenaXId()).isEqualTo("itemId"); + assertThat(tombstone.getBusinessPartnerNumber()).isEqualTo(businessPartnerNumber); + assertThat(tombstone.getProcessingError().getProcessStep()).isEqualTo(ProcessStep.USAGE_POLICY_VALIDATION); } private static PartChainIdentificationKey createKey() { From 5290a2568ed91ca6723dbc22982f262ed3a7846c Mon Sep 17 00:00:00 2001 From: Matthias Fischer Date: Thu, 1 Aug 2024 12:51:30 +0200 Subject: [PATCH 11/27] fix(exception-handling): [#841] fix PMD warning --- .../irs/registryclient/decentral/EdcRetrieverException.java | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/irs-registry-client/src/main/java/org/eclipse/tractusx/irs/registryclient/decentral/EdcRetrieverException.java b/irs-registry-client/src/main/java/org/eclipse/tractusx/irs/registryclient/decentral/EdcRetrieverException.java index 3e9a8da52..64efc443f 100644 --- a/irs-registry-client/src/main/java/org/eclipse/tractusx/irs/registryclient/decentral/EdcRetrieverException.java +++ b/irs-registry-client/src/main/java/org/eclipse/tractusx/irs/registryclient/decentral/EdcRetrieverException.java @@ -32,7 +32,7 @@ */ @Getter @Setter(AccessLevel.PRIVATE) -public class EdcRetrieverException extends Exception { +public final class EdcRetrieverException extends Exception { private String bpn; From 1c4ab9e2fce8dd4f22ef86cc8c4bcb970735b39d Mon Sep 17 00:00:00 2001 From: Matthias Fischer Date: Thu, 1 Aug 2024 14:42:50 +0200 Subject: [PATCH 12/27] fix(exception-handling): [#841] remove obsolete todos --- .../tractusx/irs/aaswrapper/job/delegate/AbstractDelegate.java | 2 +- .../ess/service/InvestigationJobProcessingEventListener.java | 2 +- .../eclipse/tractusx/irs/edc/client/EdcCallbackController.java | 2 +- 3 files changed, 3 insertions(+), 3 deletions(-) diff --git a/irs-api/src/main/java/org/eclipse/tractusx/irs/aaswrapper/job/delegate/AbstractDelegate.java b/irs-api/src/main/java/org/eclipse/tractusx/irs/aaswrapper/job/delegate/AbstractDelegate.java index d29b64494..8f8b4cb60 100644 --- a/irs-api/src/main/java/org/eclipse/tractusx/irs/aaswrapper/job/delegate/AbstractDelegate.java +++ b/irs-api/src/main/java/org/eclipse/tractusx/irs/aaswrapper/job/delegate/AbstractDelegate.java @@ -122,7 +122,7 @@ private SubmodelDescriptor getSubmodel(final EdcSubmodelFacade submodelFacade, throw new EdcClientException( String.format("Called %s connectorEndpoints but did not get any submodels. Connectors: '%s'", - connectorEndpoints.size(), String.join(", ", connectorEndpoints))); // TODO (mfischer) + connectorEndpoints.size(), String.join(", ", connectorEndpoints))); } } diff --git a/irs-api/src/main/java/org/eclipse/tractusx/irs/ess/service/InvestigationJobProcessingEventListener.java b/irs-api/src/main/java/org/eclipse/tractusx/irs/ess/service/InvestigationJobProcessingEventListener.java index 44779bd45..17742207c 100644 --- a/irs-api/src/main/java/org/eclipse/tractusx/irs/ess/service/InvestigationJobProcessingEventListener.java +++ b/irs-api/src/main/java/org/eclipse/tractusx/irs/ess/service/InvestigationJobProcessingEventListener.java @@ -203,7 +203,7 @@ private void sendNotifications(final Jobs completedJob, final BpnInvestigationJo investigationJobUpdate.withUnansweredNotifications(Collections.singletonList(new Notification(notificationId, bpn))); } catch (final EdcClientException e) { log.error("Exception during sending EDC notification.", e); - investigationJobUpdate.update(completedJob, SupplyChainImpacted.UNKNOWN); // TODO (mfischer) ??? + investigationJobUpdate.update(completedJob, SupplyChainImpacted.UNKNOWN); } }); }); diff --git a/irs-edc-client/src/main/java/org/eclipse/tractusx/irs/edc/client/EdcCallbackController.java b/irs-edc-client/src/main/java/org/eclipse/tractusx/irs/edc/client/EdcCallbackController.java index 2671384e6..a7571b7ef 100644 --- a/irs-edc-client/src/main/java/org/eclipse/tractusx/irs/edc/client/EdcCallbackController.java +++ b/irs-edc-client/src/main/java/org/eclipse/tractusx/irs/edc/client/EdcCallbackController.java @@ -70,7 +70,7 @@ public void receiveEdcCallback(final @RequestBody String endpointDataReferenceCa final String contractId = endpointDataReference.getContractId(); storeEdr(contractId, endpointDataReference); } catch (EdcClientException e) { - log.error("Could not deserialize Endpoint Data Reference {}", endpointDataReferenceCallback); // TODO (mfischer)??? + log.error("Could not deserialize Endpoint Data Reference {}", endpointDataReferenceCallback); } } From b6fd3287b43f930db223d4688600808c7d81ab40 Mon Sep 17 00:00:00 2001 From: Matthias Fischer Date: Thu, 1 Aug 2024 14:43:12 +0200 Subject: [PATCH 13/27] fix(exception-handling): [#841] fix test --- .../job/delegate/RelationshipDelegateTest.java | 11 +++++------ 1 file changed, 5 insertions(+), 6 deletions(-) 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 2e5eb5b33..540335fa1 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 @@ -412,7 +412,8 @@ void shouldCatchJsonParseExceptionAndPutTombstone() throws EdcClientException { @Test void shouldCatchUsagePolicyExceptionAndPutTombstone() throws EdcClientException { // given - final String businessPartnerNumber = "BPNL000000011111"; + + final PartChainIdentificationKey partChainIdentificationKey = createKey(); final ItemContainerBuilder itemContainerWithShell = ItemContainer.builder() .shell(shell("", shellDescriptor( List.of(submodelDescriptorWithDspEndpoint( @@ -421,9 +422,8 @@ void shouldCatchUsagePolicyExceptionAndPutTombstone() throws EdcClientException // when when(submodelFacade.getSubmodelPayload(any(), any(), any(), any())).thenThrow( - new UsagePolicyPermissionException(List.of(), null, businessPartnerNumber)); + new UsagePolicyPermissionException(List.of(), null, partChainIdentificationKey.getBpn())); when(connectorEndpointsService.fetchConnectorEndpoints(any())).thenReturn(List.of("connector.endpoint.nl")); - final PartChainIdentificationKey partChainIdentificationKey = createKey(); final ItemContainer result = relationshipDelegate.process(itemContainerWithShell, jobParameter(), new AASTransferProcess(), partChainIdentificationKey); @@ -434,12 +434,11 @@ void shouldCatchUsagePolicyExceptionAndPutTombstone() throws EdcClientException assertThat(tombstones).hasSize(1); final Tombstone tombstone = tombstones.get(0); - assertThat(tombstone.getBusinessPartnerNumber()).isEqualTo( - businessPartnerNumber); // TODO (mfischer) is this correct or should it be the bpn from partChainIdentificationKey? + assertThat(tombstone.getBusinessPartnerNumber()).isEqualTo(partChainIdentificationKey.getBpn()); assertThat(tombstone.getEndpointURL()).isEqualTo("address"); assertThat(tombstone.getCatenaXId()).isEqualTo("itemId"); - assertThat(tombstone.getBusinessPartnerNumber()).isEqualTo(businessPartnerNumber); + assertThat(tombstone.getBusinessPartnerNumber()).isEqualTo(partChainIdentificationKey.getBpn()); assertThat(tombstone.getProcessingError().getProcessStep()).isEqualTo(ProcessStep.USAGE_POLICY_VALIDATION); } From 6e309ada2b5ecb175ddfd6678aff73ef42f95007 Mon Sep 17 00:00:00 2001 From: Matthias Fischer Date: Fri, 2 Aug 2024 03:07:46 +0200 Subject: [PATCH 14/27] fix(exception-handling): [#841] add prepareEmptyCatalog --- .../SubmodelFacadeWiremockSupport.java | 32 +++++++++++++++++++ 1 file changed, 32 insertions(+) diff --git a/irs-testing/src/main/java/org/eclipse/tractusx/irs/testing/wiremock/SubmodelFacadeWiremockSupport.java b/irs-testing/src/main/java/org/eclipse/tractusx/irs/testing/wiremock/SubmodelFacadeWiremockSupport.java index ffbd23bd3..7e98522c9 100644 --- a/irs-testing/src/main/java/org/eclipse/tractusx/irs/testing/wiremock/SubmodelFacadeWiremockSupport.java +++ b/irs-testing/src/main/java/org/eclipse/tractusx/irs/testing/wiremock/SubmodelFacadeWiremockSupport.java @@ -121,6 +121,38 @@ public static void prepareFailingCatalog() { WireMockConfig.responseWithStatus(STATUS_CODE_BAD_GATEWAY).withBody(""))); } + public static void prepareEmptyCatalog(final String bpn, final String edcUrl) { + stubFor(post(urlPathEqualTo(PATH_CATALOG)).willReturn( + WireMockConfig.responseWithStatus(STATUS_CODE_OK).withBody(""" + { + "@id": "6af0d267-aaed-4d2e-86bb-adf391597fbe", + "@type": "dcat:Catalog", + "dspace:participantId": "%s", + "dcat:dataset": [], + "dcat:service": { + "@id": "75b09a2c-e7f9-4d15-bd67-334c50f35c48", + "@type": "dcat:DataService", + "dcat:endpointDescription": "dspace:connector", + "dcat:endpointUrl": "%s", + "dct:terms": "dspace:connector", + "dct:endpointUrl": "%s" + }, + "participantId": "%s", + "@context": { + "@vocab": "https://w3id.org/edc/v0.0.1/ns/", + "edc": "https://w3id.org/edc/v0.0.1/ns/", + "tx": "https://w3id.org/tractusx/v0.0.1/ns/", + "tx-auth": "https://w3id.org/tractusx/auth/", + "cx-policy": "https://w3id.org/catenax/policy/", + "dcat": "http://www.w3.org/ns/dcat#", + "dct": "http://purl.org/dc/terms/", + "odrl": "http://www.w3.org/ns/odrl/2/", + "dspace": "https://w3id.org/dspace/v0.8/" + } + } + """.formatted(bpn, edcUrl, edcUrl, bpn)))); + } + private static String startTransferProcessResponse(final String transferProcessId) { return startNegotiationResponse(transferProcessId); } From fef51fca3244f39150e728a683b64bfb58307263 Mon Sep 17 00:00:00 2001 From: Matthias Fischer Date: Fri, 2 Aug 2024 03:08:08 +0200 Subject: [PATCH 15/27] fix(exception-handling): [#841] add successfulDiscovery --- .../test/java/org/eclipse/tractusx/irs/WiremockSupport.java | 5 +++++ 1 file changed, 5 insertions(+) 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 5c73b9dc1..7ce2df78e 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 @@ -99,6 +99,11 @@ static void successfulDiscovery() { stubFor(DiscoveryServiceWiremockSupport.postEdcDiscovery200()); } + static void successfulDiscovery(final List edcUrls) { + stubFor(DiscoveryServiceWiremockSupport.postDiscoveryFinder200()); + stubFor(DiscoveryServiceWiremockSupport.postEdcDiscovery200(edcUrls)); + } + static void failedEdcDiscovery() { stubFor(DiscoveryServiceWiremockSupport.postDiscoveryFinder200()); stubFor(DiscoveryServiceWiremockSupport.postEdcDiscovery200Empty()); From 147f7016e960851d7c6d2759e59b4dd0a3485e9a Mon Sep 17 00:00:00 2001 From: Matthias Fischer Date: Fri, 2 Aug 2024 03:10:54 +0200 Subject: [PATCH 16/27] fix(exception-handling): [#841] add methods for multiple edc urls --- .../DiscoveryServiceWiremockSupport.java | 46 +++++++++++++++---- 1 file changed, 36 insertions(+), 10 deletions(-) diff --git a/irs-testing/src/main/java/org/eclipse/tractusx/irs/testing/wiremock/DiscoveryServiceWiremockSupport.java b/irs-testing/src/main/java/org/eclipse/tractusx/irs/testing/wiremock/DiscoveryServiceWiremockSupport.java index fdfa23ff5..a4c91d3d3 100644 --- a/irs-testing/src/main/java/org/eclipse/tractusx/irs/testing/wiremock/DiscoveryServiceWiremockSupport.java +++ b/irs-testing/src/main/java/org/eclipse/tractusx/irs/testing/wiremock/DiscoveryServiceWiremockSupport.java @@ -23,7 +23,10 @@ import static com.github.tomakehurst.wiremock.client.WireMock.urlPathEqualTo; import static org.eclipse.tractusx.irs.testing.wiremock.WireMockConfig.responseWithStatus; +import java.util.Arrays; import java.util.List; +import java.util.UUID; +import java.util.stream.Collectors; import com.github.tomakehurst.wiremock.client.MappingBuilder; @@ -45,7 +48,15 @@ private DiscoveryServiceWiremockSupport() { } public static MappingBuilder postEdcDiscovery200() { - return postEdcDiscovery200(TEST_BPN, List.of(CONTROLPLANE_PUBLIC_URL)); + return postEdcDiscovery200(List.of(CONTROLPLANE_PUBLIC_URL)); + } + + public static MappingBuilder postEdcDiscovery200(final String... edcUrls) { + return postEdcDiscovery200(Arrays.asList(edcUrls)); + } + + public static MappingBuilder postEdcDiscovery200(final List edcUrls) { + return postEdcDiscovery200(TEST_BPN, edcUrls); } public static MappingBuilder postEdcDiscovery200Empty() { @@ -79,20 +90,35 @@ public static MappingBuilder postDiscoveryFinder200() { responseWithStatus(STATUS_CODE_OK).withBody(discoveryFinderResponse(EDC_DISCOVERY_URL))); } - public static String discoveryFinderResponse(final String discoveryFinderUrl) { + public static MappingBuilder postDiscoveryFinder200(final String... edcUrls) { + return post(urlPathEqualTo(DISCOVERY_FINDER_PATH)).willReturn( + responseWithStatus(STATUS_CODE_OK).withBody(discoveryFinderResponse(edcUrls))); + } + + public static String discoveryFinderResponse(final String... discoveryFinderUrls) { + + final String endpoints = Arrays.stream(discoveryFinderUrls) + .map(endpointAddress -> { + final String resourceId = UUID.randomUUID().toString(); + return """ + { + "type": "bpn", + "description": "Service to discover EDC to a particular BPN", + "endpointAddress": "%s", + "documentation": "http://.../swagger/index.html", + "resourceId": "%s" + } + """.formatted(endpointAddress, resourceId); + }) + .collect(Collectors.joining(",")); + return """ { "endpoints": [ - { - "type": "bpn", - "description": "Service to discover EDC to a particular BPN", - "endpointAddress": "%s", - "documentation": "http://.../swagger/index.html", - "resourceId": "316417cd-0fb5-4daf-8dfa-8f68125923f1" - } + %s ] } - """.formatted(discoveryFinderUrl); + """.formatted(endpoints); } public static MappingBuilder postDiscoveryFinder404() { From d3c8af708431eb583f26b179697019dc34a06c69 Mon Sep 17 00:00:00 2001 From: Matthias Fischer Date: Fri, 2 Aug 2024 03:11:34 +0200 Subject: [PATCH 17/27] fix(exception-handling): [#841] remove todo --- .../irs/aaswrapper/job/delegate/DigitalTwinDelegate.java | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) diff --git a/irs-api/src/main/java/org/eclipse/tractusx/irs/aaswrapper/job/delegate/DigitalTwinDelegate.java b/irs-api/src/main/java/org/eclipse/tractusx/irs/aaswrapper/job/delegate/DigitalTwinDelegate.java index 826ceb729..29e600f9e 100644 --- a/irs-api/src/main/java/org/eclipse/tractusx/irs/aaswrapper/job/delegate/DigitalTwinDelegate.java +++ b/irs-api/src/main/java/org/eclipse/tractusx/irs/aaswrapper/job/delegate/DigitalTwinDelegate.java @@ -110,10 +110,12 @@ private void createShellEndpointCouldNotBeRetrievedTombstone( .withErrorDetail(exception.getMessage()) .withRootCauses(rootErrorMessages) .build(); - String endpointURL = null; // TODO (mfischer) test + + String endpointURL = null; if (exception instanceof ShellNotFoundException) { endpointURL = String.join("; ", ((ShellNotFoundException) exception).getCalledEndpoints()); } + final Tombstone tombstone = Tombstone.builder() .endpointURL(endpointURL) .catenaXId(itemId.getGlobalAssetId()) @@ -123,7 +125,6 @@ private void createShellEndpointCouldNotBeRetrievedTombstone( itemContainerBuilder.tombstone(tombstone); } - private Tombstone createNoBpnProvidedTombstone(final JobParameter jobData, final PartChainIdentificationKey itemId) { log.warn("Could not process item with id {} because no BPN was provided. Creating Tombstone.", From d89603bb67ede0b5a254baa2e4d9bb9d65c6b617 Mon Sep 17 00:00:00 2001 From: Matthias Fischer Date: Fri, 2 Aug 2024 03:36:58 +0200 Subject: [PATCH 18/27] fix(exception-handling): [#841] add test --- .../irs/IrsWireMockIntegrationTest.java | 44 ++++++++++++++++++- 1 file changed, 42 insertions(+), 2 deletions(-) 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 b425bc879..98639e33e 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 @@ -374,6 +374,43 @@ void shouldCreateDetailedTombstoneForEdcErrors() { assertThat(rootCauses.get(0)).contains("502 Bad Gateway"); } + @Test + void whenEmptyCatalogIsReturnedFromAllEndpoints() { + // Arrange + final String globalAssetId = "urn:uuid:334cce52-1f52-4bc9-9dd1-410bbe497bbc"; + final List edcUrls = List.of("https://test.edc1.io", "https://test.edc2.io"); + + WiremockSupport.successfulSemanticModelRequest(); + WiremockSupport.successfulSemanticHubRequests(); + WiremockSupport.successfulDiscovery(edcUrls); + + edcUrls.forEach(edcUrl -> emptyCatalog(TEST_BPN, edcUrl)); + + // Act + final RegisterJob request = WiremockSupport.jobRequest(globalAssetId, TEST_BPN, 4); + final JobHandle jobHandle = irsService.registerItemJob(request); + assertThat(jobHandle.getId()).isNotNull(); + waitForCompletion(jobHandle); + final Jobs jobForJobId = irsService.getJobForJobId(jobHandle.getId(), false); + + // Assert + + assertThat(jobForJobId.getJob().getState()).isEqualTo(JobState.COMPLETED); + + assertThat(jobForJobId.getShells()).isEmpty(); + assertThat(jobForJobId.getRelationships()).isEmpty(); + assertThat(jobForJobId.getSubmodels()).isEmpty(); + + final List tombstones = jobForJobId.getTombstones(); + assertThat(tombstones).hasSize(1); + + final Tombstone actualTombstone = tombstones.get(0); + assertThat(actualTombstone.getBusinessPartnerNumber()).isEqualTo(TEST_BPN); + assertThat(actualTombstone.getEndpointURL()).describedAs("Tombstone should contain all EDC URLs") + .isEqualTo(String.join("; ", edcUrls)); + + } + @Test void shouldCreateDetailedTombstoneForDiscoveryErrors() { // Arrange @@ -410,8 +447,7 @@ void shouldCreateDetailedTombstoneForDiscoveryErrors() { final List rootCauses = actualTombstone.getProcessingError().getRootCauses(); assertThat(rootCauses).hasSize(1); - assertThat(rootCauses.get(0)).contains( - "No EDC Endpoints could be discovered for BPN '%s'".formatted(TEST_BPN)); + assertThat(rootCauses.get(0)).contains("No EDC Endpoints could be discovered for BPN '%s'".formatted(TEST_BPN)); } private void successfulRegistryAndDataRequest(final String globalAssetId, final String idShort, final String bpn, @@ -462,6 +498,10 @@ private void failedNegotiation() { SubmodelFacadeWiremockSupport.prepareFailingCatalog(); } + private void emptyCatalog(final String bpn, final String edcUrl) { + SubmodelFacadeWiremockSupport.prepareEmptyCatalog(bpn, edcUrl); + } + private void waitForCompletion(final JobHandle jobHandle) { Awaitility.await() .timeout(Duration.ofSeconds(35)) From 76d22fa304ef8930f3dd307c281136a76eaa2c8a Mon Sep 17 00:00:00 2001 From: Matthias Fischer Date: Fri, 2 Aug 2024 04:32:19 +0200 Subject: [PATCH 19/27] fix(exception-handling): [#841] add todo --- .../irs/aaswrapper/job/delegate/DigitalTwinDelegate.java | 2 ++ 1 file changed, 2 insertions(+) diff --git a/irs-api/src/main/java/org/eclipse/tractusx/irs/aaswrapper/job/delegate/DigitalTwinDelegate.java b/irs-api/src/main/java/org/eclipse/tractusx/irs/aaswrapper/job/delegate/DigitalTwinDelegate.java index 29e600f9e..97a4ea6dd 100644 --- a/irs-api/src/main/java/org/eclipse/tractusx/irs/aaswrapper/job/delegate/DigitalTwinDelegate.java +++ b/irs-api/src/main/java/org/eclipse/tractusx/irs/aaswrapper/job/delegate/DigitalTwinDelegate.java @@ -101,7 +101,9 @@ private void createShellEndpointCouldNotBeRetrievedTombstone( final ItemContainer.ItemContainerBuilder itemContainerBuilder, final PartChainIdentificationKey itemId, final Exception exception) { + // TODO (mfischer) is this log message and method name correct? log.info("Shell Endpoint could not be retrieved for Item: {}. Creating Tombstone.", itemId); + log.debug(exception.getMessage(), exception); final List rootErrorMessages = Tombstone.getRootErrorMessages(exception.getSuppressed()); final ProcessingError error = ProcessingError.builder() From d6f692040b13a68c3df40cd5feff0f428f01e123 Mon Sep 17 00:00:00 2001 From: Matthias Fischer Date: Fri, 2 Aug 2024 04:47:47 +0200 Subject: [PATCH 20/27] fix(exception-handling): [#841] PMD --- .../irs/testing/wiremock/DiscoveryServiceWiremockSupport.java | 1 + .../irs/testing/wiremock/SubmodelFacadeWiremockSupport.java | 1 + 2 files changed, 2 insertions(+) diff --git a/irs-testing/src/main/java/org/eclipse/tractusx/irs/testing/wiremock/DiscoveryServiceWiremockSupport.java b/irs-testing/src/main/java/org/eclipse/tractusx/irs/testing/wiremock/DiscoveryServiceWiremockSupport.java index a4c91d3d3..41f6dcc03 100644 --- a/irs-testing/src/main/java/org/eclipse/tractusx/irs/testing/wiremock/DiscoveryServiceWiremockSupport.java +++ b/irs-testing/src/main/java/org/eclipse/tractusx/irs/testing/wiremock/DiscoveryServiceWiremockSupport.java @@ -33,6 +33,7 @@ /** * WireMock configurations and requests used for testing the Discovery Service flow. */ +@SuppressWarnings("PMD.TooManyMethods") public final class DiscoveryServiceWiremockSupport { public static final String CONTROLPLANE_PUBLIC_URL = "https://test.edc.io"; public static final String EDC_DISCOVERY_PATH = "/edcDiscovery"; diff --git a/irs-testing/src/main/java/org/eclipse/tractusx/irs/testing/wiremock/SubmodelFacadeWiremockSupport.java b/irs-testing/src/main/java/org/eclipse/tractusx/irs/testing/wiremock/SubmodelFacadeWiremockSupport.java index 7e98522c9..c95aa4074 100644 --- a/irs-testing/src/main/java/org/eclipse/tractusx/irs/testing/wiremock/SubmodelFacadeWiremockSupport.java +++ b/irs-testing/src/main/java/org/eclipse/tractusx/irs/testing/wiremock/SubmodelFacadeWiremockSupport.java @@ -29,6 +29,7 @@ /** * WireMock configurations and requests used for testing the EDC Flow. */ +@SuppressWarnings("PMD.TooManyMethods") public final class SubmodelFacadeWiremockSupport { public static final String PATH_CATALOG = "/catalog/request"; public static final String PATH_NEGOTIATE = "/contractnegotiations"; From 1a6a677f4612de304024ecc2607d206bf874c845 Mon Sep 17 00:00:00 2001 From: Matthias Fischer Date: Fri, 2 Aug 2024 11:05:12 +0200 Subject: [PATCH 21/27] fix(exception-handling): [#841] fix checkstyle warning replace tabs with spaces --- .../SubmodelFacadeWiremockSupport.java | 48 +++++++++---------- 1 file changed, 24 insertions(+), 24 deletions(-) diff --git a/irs-testing/src/main/java/org/eclipse/tractusx/irs/testing/wiremock/SubmodelFacadeWiremockSupport.java b/irs-testing/src/main/java/org/eclipse/tractusx/irs/testing/wiremock/SubmodelFacadeWiremockSupport.java index c95aa4074..86abd6742 100644 --- a/irs-testing/src/main/java/org/eclipse/tractusx/irs/testing/wiremock/SubmodelFacadeWiremockSupport.java +++ b/irs-testing/src/main/java/org/eclipse/tractusx/irs/testing/wiremock/SubmodelFacadeWiremockSupport.java @@ -126,30 +126,30 @@ public static void prepareEmptyCatalog(final String bpn, final String edcUrl) { stubFor(post(urlPathEqualTo(PATH_CATALOG)).willReturn( WireMockConfig.responseWithStatus(STATUS_CODE_OK).withBody(""" { - "@id": "6af0d267-aaed-4d2e-86bb-adf391597fbe", - "@type": "dcat:Catalog", - "dspace:participantId": "%s", - "dcat:dataset": [], - "dcat:service": { - "@id": "75b09a2c-e7f9-4d15-bd67-334c50f35c48", - "@type": "dcat:DataService", - "dcat:endpointDescription": "dspace:connector", - "dcat:endpointUrl": "%s", - "dct:terms": "dspace:connector", - "dct:endpointUrl": "%s" - }, - "participantId": "%s", - "@context": { - "@vocab": "https://w3id.org/edc/v0.0.1/ns/", - "edc": "https://w3id.org/edc/v0.0.1/ns/", - "tx": "https://w3id.org/tractusx/v0.0.1/ns/", - "tx-auth": "https://w3id.org/tractusx/auth/", - "cx-policy": "https://w3id.org/catenax/policy/", - "dcat": "http://www.w3.org/ns/dcat#", - "dct": "http://purl.org/dc/terms/", - "odrl": "http://www.w3.org/ns/odrl/2/", - "dspace": "https://w3id.org/dspace/v0.8/" - } + "@id": "6af0d267-aaed-4d2e-86bb-adf391597fbe", + "@type": "dcat:Catalog", + "dspace:participantId": "%s", + "dcat:dataset": [], + "dcat:service": { + "@id": "75b09a2c-e7f9-4d15-bd67-334c50f35c48", + "@type": "dcat:DataService", + "dcat:endpointDescription": "dspace:connector", + "dcat:endpointUrl": "%s", + "dct:terms": "dspace:connector", + "dct:endpointUrl": "%s" + }, + "participantId": "%s", + "@context": { + "@vocab": "https://w3id.org/edc/v0.0.1/ns/", + "edc": "https://w3id.org/edc/v0.0.1/ns/", + "tx": "https://w3id.org/tractusx/v0.0.1/ns/", + "tx-auth": "https://w3id.org/tractusx/auth/", + "cx-policy": "https://w3id.org/catenax/policy/", + "dcat": "http://www.w3.org/ns/dcat#", + "dct": "http://purl.org/dc/terms/", + "odrl": "http://www.w3.org/ns/odrl/2/", + "dspace": "https://w3id.org/dspace/v0.8/" + } } """.formatted(bpn, edcUrl, edcUrl, bpn)))); } From 3d43c2cbc8d6f0de496e9327988f67ad5c06ae25 Mon Sep 17 00:00:00 2001 From: Matthias Fischer Date: Fri, 2 Aug 2024 11:09:39 +0200 Subject: [PATCH 22/27] Update irs-testing/src/main/java/org/eclipse/tractusx/irs/testing/wiremock/DiscoveryServiceWiremockSupport.java Co-authored-by: Jaro Hartmann <57985712+ds-jhartmann@users.noreply.github.com> --- .../irs/testing/wiremock/DiscoveryServiceWiremockSupport.java | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/irs-testing/src/main/java/org/eclipse/tractusx/irs/testing/wiremock/DiscoveryServiceWiremockSupport.java b/irs-testing/src/main/java/org/eclipse/tractusx/irs/testing/wiremock/DiscoveryServiceWiremockSupport.java index 41f6dcc03..9a7ad8b37 100644 --- a/irs-testing/src/main/java/org/eclipse/tractusx/irs/testing/wiremock/DiscoveryServiceWiremockSupport.java +++ b/irs-testing/src/main/java/org/eclipse/tractusx/irs/testing/wiremock/DiscoveryServiceWiremockSupport.java @@ -91,7 +91,7 @@ public static MappingBuilder postDiscoveryFinder200() { responseWithStatus(STATUS_CODE_OK).withBody(discoveryFinderResponse(EDC_DISCOVERY_URL))); } - public static MappingBuilder postDiscoveryFinder200(final String... edcUrls) { + public static MappingBuilder postDiscoveryFinder200(final String... discoveryFinderUrls) { return post(urlPathEqualTo(DISCOVERY_FINDER_PATH)).willReturn( responseWithStatus(STATUS_CODE_OK).withBody(discoveryFinderResponse(edcUrls))); } From 5ed2f8964576b5ee4fdd5ccd28013dd24bf8266e Mon Sep 17 00:00:00 2001 From: Matthias Fischer Date: Fri, 2 Aug 2024 11:16:12 +0200 Subject: [PATCH 23/27] fix(exception-handling): [#841] improve test --- .../decentral/EdcRetrieverExceptionTest.java | 20 ++++++++++--------- 1 file changed, 11 insertions(+), 9 deletions(-) diff --git a/irs-registry-client/src/test/java/org/eclipse/tractusx/irs/registryclient/decentral/EdcRetrieverExceptionTest.java b/irs-registry-client/src/test/java/org/eclipse/tractusx/irs/registryclient/decentral/EdcRetrieverExceptionTest.java index 2a9c9cef5..08a90515c 100644 --- a/irs-registry-client/src/test/java/org/eclipse/tractusx/irs/registryclient/decentral/EdcRetrieverExceptionTest.java +++ b/irs-registry-client/src/test/java/org/eclipse/tractusx/irs/registryclient/decentral/EdcRetrieverExceptionTest.java @@ -21,23 +21,25 @@ import static org.assertj.core.api.Assertions.assertThat; -import org.assertj.core.api.Assertions; import org.junit.jupiter.api.Test; class EdcRetrieverExceptionTest { @Test - void test() throws EdcRetrieverException { + void testBuildEdcRetrieverException() { - final EdcRetrieverException build = new EdcRetrieverException.Builder( - new IllegalArgumentException("my illegal arg")).withEdcUrl("my url").withBpn("my bpn").build(); + final String causeMessage = "my illegal arg"; + final IllegalArgumentException cause = new IllegalArgumentException(causeMessage); + final String expectedUrl = "my url"; + final String expectedBpn = "my bpn"; - assertThat(build.getBpn()).isEqualTo("my bpn"); - assertThat(build.getEdcUrl()).isEqualTo("my url"); + final EdcRetrieverException builtException = new EdcRetrieverException.Builder(cause).withEdcUrl(expectedUrl) + .withBpn(expectedBpn) + .build(); - Assertions.assertThatThrownBy(() -> { - throw build; - }).hasMessageContaining("my illegal arg"); + assertThat(builtException.getBpn()).isEqualTo(expectedBpn); + assertThat(builtException.getEdcUrl()).isEqualTo(expectedUrl); + assertThat(builtException).hasMessageContaining(causeMessage); } } \ No newline at end of file From 13a23e6f139d9e11090cff0965766053a794df73 Mon Sep 17 00:00:00 2001 From: Matthias Fischer Date: Fri, 2 Aug 2024 13:03:54 +0200 Subject: [PATCH 24/27] fix(exception-handling): [#841] fix compile error --- .../DiscoveryServiceWiremockSupport.java | 28 +++++++++---------- 1 file changed, 13 insertions(+), 15 deletions(-) diff --git a/irs-testing/src/main/java/org/eclipse/tractusx/irs/testing/wiremock/DiscoveryServiceWiremockSupport.java b/irs-testing/src/main/java/org/eclipse/tractusx/irs/testing/wiremock/DiscoveryServiceWiremockSupport.java index 9a7ad8b37..d05f28e4a 100644 --- a/irs-testing/src/main/java/org/eclipse/tractusx/irs/testing/wiremock/DiscoveryServiceWiremockSupport.java +++ b/irs-testing/src/main/java/org/eclipse/tractusx/irs/testing/wiremock/DiscoveryServiceWiremockSupport.java @@ -93,25 +93,23 @@ public static MappingBuilder postDiscoveryFinder200() { public static MappingBuilder postDiscoveryFinder200(final String... discoveryFinderUrls) { return post(urlPathEqualTo(DISCOVERY_FINDER_PATH)).willReturn( - responseWithStatus(STATUS_CODE_OK).withBody(discoveryFinderResponse(edcUrls))); + responseWithStatus(STATUS_CODE_OK).withBody(discoveryFinderResponse(discoveryFinderUrls))); } public static String discoveryFinderResponse(final String... discoveryFinderUrls) { - final String endpoints = Arrays.stream(discoveryFinderUrls) - .map(endpointAddress -> { - final String resourceId = UUID.randomUUID().toString(); - return """ - { - "type": "bpn", - "description": "Service to discover EDC to a particular BPN", - "endpointAddress": "%s", - "documentation": "http://.../swagger/index.html", - "resourceId": "%s" - } - """.formatted(endpointAddress, resourceId); - }) - .collect(Collectors.joining(",")); + final String endpoints = Arrays.stream(discoveryFinderUrls).map(endpointAddress -> { + final String resourceId = UUID.randomUUID().toString(); + return """ + { + "type": "bpn", + "description": "Service to discover EDC to a particular BPN", + "endpointAddress": "%s", + "documentation": "http://.../swagger/index.html", + "resourceId": "%s" + } + """.formatted(endpointAddress, resourceId); + }).collect(Collectors.joining(",")); return """ { From e9472680a3cfecfcc56026763652fc0311834600 Mon Sep 17 00:00:00 2001 From: Matthias Fischer Date: Fri, 2 Aug 2024 13:05:50 +0200 Subject: [PATCH 25/27] fix(exception-handling): [#841] add changelog entry --- CHANGELOG.md | 1 + 1 file changed, 1 insertion(+) diff --git a/CHANGELOG.md b/CHANGELOG.md index 2a0aada3c..9d9c67b52 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -17,6 +17,7 @@ _**For better traceability add the corresponding GitHub issue number in each cha in some remaining code places (in context of #794). - Fixed flaky test `InMemoryJobStoreTest.checkLastModifiedOnAfterCreation()` (PR#857). - Fixed occasion where completed Job callbacks are called multiple times. #755 +- BPN and endpointURL(s) are set in the tombstone now. #841 ### Changed From bbe6fef05690fdf8911e216341e2f46bfa0a8d4a Mon Sep 17 00:00:00 2001 From: Matthias Fischer Date: Fri, 2 Aug 2024 13:10:21 +0200 Subject: [PATCH 26/27] fix(exception-handling): [#841] use unused method --- .../irs/testing/wiremock/DiscoveryServiceWiremockSupport.java | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/irs-testing/src/main/java/org/eclipse/tractusx/irs/testing/wiremock/DiscoveryServiceWiremockSupport.java b/irs-testing/src/main/java/org/eclipse/tractusx/irs/testing/wiremock/DiscoveryServiceWiremockSupport.java index d05f28e4a..e2c803304 100644 --- a/irs-testing/src/main/java/org/eclipse/tractusx/irs/testing/wiremock/DiscoveryServiceWiremockSupport.java +++ b/irs-testing/src/main/java/org/eclipse/tractusx/irs/testing/wiremock/DiscoveryServiceWiremockSupport.java @@ -49,7 +49,7 @@ private DiscoveryServiceWiremockSupport() { } public static MappingBuilder postEdcDiscovery200() { - return postEdcDiscovery200(List.of(CONTROLPLANE_PUBLIC_URL)); + return postEdcDiscovery200(CONTROLPLANE_PUBLIC_URL); } public static MappingBuilder postEdcDiscovery200(final String... edcUrls) { From 5715943490ff62234c96859b145e25b3ea6197e3 Mon Sep 17 00:00:00 2001 From: Matthias Fischer Date: Fri, 2 Aug 2024 13:52:05 +0200 Subject: [PATCH 27/27] fix(exception-handling): [#841] resolve todo --- .../job/delegate/DigitalTwinDelegate.java | 37 +++++++++++-------- 1 file changed, 22 insertions(+), 15 deletions(-) diff --git a/irs-api/src/main/java/org/eclipse/tractusx/irs/aaswrapper/job/delegate/DigitalTwinDelegate.java b/irs-api/src/main/java/org/eclipse/tractusx/irs/aaswrapper/job/delegate/DigitalTwinDelegate.java index 97a4ea6dd..7e01a789c 100644 --- a/irs-api/src/main/java/org/eclipse/tractusx/irs/aaswrapper/job/delegate/DigitalTwinDelegate.java +++ b/irs-api/src/main/java/org/eclipse/tractusx/irs/aaswrapper/job/delegate/DigitalTwinDelegate.java @@ -83,9 +83,13 @@ public ItemContainer process(final ItemContainer.ItemContainerBuilder itemContai itemContainerBuilder.shell( jobData.isAuditContractNegotiation() ? shell : shell.withoutContractAgreementId()); + } catch (final ShellNotFoundException e) { + log.info("Shell not found for item: {}. Creating Tombstone.", itemId); + createShellNotFoundTombstone(itemContainerBuilder, itemId, e); } catch (final RegistryServiceException | RuntimeException e) { // catching generic exception is intended here, // otherwise Jobs stay in state RUNNING forever + log.info("Shell could not be retrieved for item: {}. Creating Tombstone.", itemId); createShellEndpointCouldNotBeRetrievedTombstone(itemContainerBuilder, itemId, e); } @@ -97,13 +101,22 @@ public ItemContainer process(final ItemContainer.ItemContainerBuilder itemContai return itemContainerBuilder.build(); } + private void createShellNotFoundTombstone(final ItemContainer.ItemContainerBuilder itemContainerBuilder, + final PartChainIdentificationKey itemId, final ShellNotFoundException exception) { + final String endpointURL = String.join("; ", exception.getCalledEndpoints()); + final Tombstone tombstone = createTombstone(itemId, exception, endpointURL); + itemContainerBuilder.tombstone(tombstone); + } + private void createShellEndpointCouldNotBeRetrievedTombstone( final ItemContainer.ItemContainerBuilder itemContainerBuilder, final PartChainIdentificationKey itemId, final Exception exception) { + final Tombstone tombstone = createTombstone(itemId, exception, /* endpoint URL is unknown here */ null); + itemContainerBuilder.tombstone(tombstone); + } - // TODO (mfischer) is this log message and method name correct? - log.info("Shell Endpoint could not be retrieved for Item: {}. Creating Tombstone.", itemId); - log.debug(exception.getMessage(), exception); + private Tombstone createTombstone(final PartChainIdentificationKey itemId, final Exception exception, + final String endpointURL) { final List rootErrorMessages = Tombstone.getRootErrorMessages(exception.getSuppressed()); final ProcessingError error = ProcessingError.builder() @@ -113,18 +126,12 @@ private void createShellEndpointCouldNotBeRetrievedTombstone( .withRootCauses(rootErrorMessages) .build(); - String endpointURL = null; - if (exception instanceof ShellNotFoundException) { - endpointURL = String.join("; ", ((ShellNotFoundException) exception).getCalledEndpoints()); - } - - final Tombstone tombstone = Tombstone.builder() - .endpointURL(endpointURL) - .catenaXId(itemId.getGlobalAssetId()) - .processingError(error) - .businessPartnerNumber(itemId.getBpn()) - .build(); - itemContainerBuilder.tombstone(tombstone); + return Tombstone.builder() + .endpointURL(endpointURL) + .catenaXId(itemId.getGlobalAssetId()) + .processingError(error) + .businessPartnerNumber(itemId.getBpn()) + .build(); } private Tombstone createNoBpnProvidedTombstone(final JobParameter jobData,