Skip to content

Commit

Permalink
fix(exception-handling): [eclipse-tractusx#841] Fixes, improvements, …
Browse files Browse the repository at this point in the history
…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.
  • Loading branch information
dsmf committed Jul 31, 2024
1 parent f2d88aa commit 543daf6
Show file tree
Hide file tree
Showing 16 changed files with 350 additions and 202 deletions.
Original file line number Diff line number Diff line change
Expand Up @@ -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)
}

}
Original file line number Diff line number Diff line change
Expand Up @@ -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}.
Expand Down Expand Up @@ -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())) {
Expand All @@ -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<String> 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());

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -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;
Expand All @@ -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;
Expand All @@ -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;
Expand Down Expand Up @@ -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;
}

Expand All @@ -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<String, Object> 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<Bpn> getBpnsFrom(final List<Relationship> relationships) {
return relationships.stream()
.map(Relationship::getBpn)
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -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;
Expand Down Expand Up @@ -107,17 +107,21 @@ public ItemContainer process(final ItemContainer.ItemContainerBuilder itemContai
private List<Submodel> getSubmodels(final SubmodelDescriptor submodelDescriptor,
final ItemContainer.ItemContainerBuilder itemContainerBuilder, final String itemId, final String bpn,
final boolean auditContractNegotiation) {

final List<Submodel> 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);
Expand All @@ -129,34 +133,71 @@ private List<Submodel> 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<String, Object> 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) {
Expand Down
Loading

0 comments on commit 543daf6

Please sign in to comment.