From 543daf6923cff7fc82885180cbf2e6b1cd016b99 Mon Sep 17 00:00:00 2001 From: Matthias Fischer Date: Thu, 1 Aug 2024 01:44:44 +0200 Subject: [PATCH] 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 e78a394b29..3ce5faaf59 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 55f5fc4132..826ceb729f 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 f45dca48e2..1516a3c571 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 ca92b2f799..8a89e63c78 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 032758c0ab..ec10c41d4a 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 a8d30d1af6..47a612d8a0 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 17742207c1..44779bd451 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 4f70fb6c7b..5ee8b3c6ef 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 bc9a42531c..6d8db2d077 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 c62e5d8b54..830c548375 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 a7571b7ef9..2671384e68 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 1d2f486271..7ca62db58f 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 a15eeb7221..230c66d7ee 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 a71abc4d6e..6e6d4b3ed5 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 915a384f8b..f34da42154 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 30163c000f..865fc43740 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)); + } }