From 91e03fc27c03ca745d69e51c7efdd578c727e977 Mon Sep 17 00:00:00 2001 From: Enrico Risa Date: Sat, 12 Aug 2023 08:47:14 +0200 Subject: [PATCH] feat(edrs): add EDR api schema and example (#705) --- edc-extensions/edr/edr-api/build.gradle.kts | 1 + .../eclipse/tractusx/edc/api/edr/EdrApi.java | 14 +-- .../edc/api/edr/schema/EdrSchema.java | 104 ++++++++++++++++ .../tractusx/edc/api/edr/EdrApiTest.java | 117 ++++++++++++++++++ gradle/libs.versions.toml | 1 + 5 files changed, 229 insertions(+), 8 deletions(-) create mode 100644 edc-extensions/edr/edr-api/src/main/java/org/eclipse/tractusx/edc/api/edr/schema/EdrSchema.java create mode 100644 edc-extensions/edr/edr-api/src/test/java/org/eclipse/tractusx/edc/api/edr/EdrApiTest.java diff --git a/edc-extensions/edr/edr-api/build.gradle.kts b/edc-extensions/edr/edr-api/build.gradle.kts index 07a00f12f..cc3e8cad7 100644 --- a/edc-extensions/edr/edr-api/build.gradle.kts +++ b/edc-extensions/edr/edr-api/build.gradle.kts @@ -32,4 +32,5 @@ dependencies { testImplementation(libs.restAssured) testImplementation(libs.edc.junit) testImplementation(libs.edc.ext.jersey.providers) + testImplementation(libs.edc.core.transform) } diff --git a/edc-extensions/edr/edr-api/src/main/java/org/eclipse/tractusx/edc/api/edr/EdrApi.java b/edc-extensions/edr/edr-api/src/main/java/org/eclipse/tractusx/edc/api/edr/EdrApi.java index 841c942dc..400857ccb 100644 --- a/edc-extensions/edr/edr-api/src/main/java/org/eclipse/tractusx/edc/api/edr/EdrApi.java +++ b/edc-extensions/edr/edr-api/src/main/java/org/eclipse/tractusx/edc/api/edr/EdrApi.java @@ -26,8 +26,7 @@ import org.eclipse.edc.api.model.ApiCoreSchema; import org.eclipse.edc.connector.api.management.configuration.ManagementApiSchema; import org.eclipse.edc.web.spi.ApiErrorDetail; -import org.eclipse.tractusx.edc.api.edr.dto.NegotiateEdrRequestDto; -import org.eclipse.tractusx.edc.edr.spi.types.EndpointDataReferenceEntry; +import org.eclipse.tractusx.edc.api.edr.schema.EdrSchema; @OpenAPIDefinition @Tag(name = "Control Plane EDR Api") @@ -41,18 +40,18 @@ public interface EdrApi { @ApiResponse(responseCode = "400", description = "Request body was malformed", content = @Content(array = @ArraySchema(schema = @Schema(implementation = ApiErrorDetail.class)))), }) - JsonObject initiateEdrNegotiation(@Schema(implementation = NegotiateEdrRequestDto.class) JsonObject dto); + JsonObject initiateEdrNegotiation(@Schema(implementation = EdrSchema.NegotiateEdrRequestSchema.class) JsonObject dto); @Operation(description = "Returns all EndpointDataReference entry according to a query", responses = { @ApiResponse(responseCode = "200", - content = @Content(array = @ArraySchema(schema = @Schema(implementation = EndpointDataReferenceEntry.class)))), + content = @Content(array = @ArraySchema(schema = @Schema(implementation = EdrSchema.EndpointDataReferenceEntrySchema.class)))), @ApiResponse(responseCode = "400", description = "Request was malformed", content = @Content(array = @ArraySchema(schema = @Schema(implementation = ApiErrorDetail.class)))) } ) JsonArray queryEdrs(String assetId, String agreementId, String providerId); - @Operation(description = "Gets an EDR with the given transfer process ID", + @Operation(description = "Gets an EDR with the given transfer process ID", responses = { @ApiResponse(responseCode = "200", description = "The EDR cached", content = @Content(schema = @Schema(implementation = ManagementApiSchema.DataAddressSchema.class))), @@ -64,10 +63,9 @@ public interface EdrApi { ) JsonObject getEdr(String transferProcessId); - @Operation(description = "Delete an EDR with the given transfer process ID", + @Operation(description = "Delete an EDR with the given transfer process ID", responses = { - @ApiResponse(responseCode = "200", description = "The EDR cached", - content = @Content(schema = @Schema(implementation = ManagementApiSchema.DataAddressSchema.class))), + @ApiResponse(responseCode = "200", description = "The EDR cached was deleted successfully"), @ApiResponse(responseCode = "400", description = "Request was malformed, e.g. id was null", content = @Content(array = @ArraySchema(schema = @Schema(implementation = ApiErrorDetail.class)))), @ApiResponse(responseCode = "404", description = "An EDR with the given ID does not exist", diff --git a/edc-extensions/edr/edr-api/src/main/java/org/eclipse/tractusx/edc/api/edr/schema/EdrSchema.java b/edc-extensions/edr/edr-api/src/main/java/org/eclipse/tractusx/edc/api/edr/schema/EdrSchema.java new file mode 100644 index 000000000..3c1d6aa4f --- /dev/null +++ b/edc-extensions/edr/edr-api/src/main/java/org/eclipse/tractusx/edc/api/edr/schema/EdrSchema.java @@ -0,0 +1,104 @@ +/* + * Copyright (c) 2023 Bayerische Motoren Werke Aktiengesellschaft (BMW AG) + * + * 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 + * + * SPDX-License-Identifier: Apache-2.0 + * + * Contributors: + * Bayerische Motoren Werke Aktiengesellschaft (BMW AG) - initial API and implementation + * + */ + +package org.eclipse.tractusx.edc.api.edr.schema; + +import io.swagger.v3.oas.annotations.media.Schema; +import org.eclipse.edc.connector.api.management.configuration.ManagementApiSchema; +import org.eclipse.edc.connector.api.management.contractnegotiation.ContractNegotiationApi; +import org.eclipse.tractusx.edc.edr.spi.types.EndpointDataReferenceEntry; + +import java.util.List; + +import static org.eclipse.edc.jsonld.spi.JsonLdKeywords.TYPE; +import static org.eclipse.tractusx.edc.api.edr.dto.NegotiateEdrRequestDto.EDR_REQUEST_DTO_TYPE; +import static org.eclipse.tractusx.edc.api.edr.schema.EdrSchema.EndpointDataReferenceEntrySchema.ENDPOINT_DATA_REFERENCE_ENTRY_EXAMPLE; +import static org.eclipse.tractusx.edc.api.edr.schema.EdrSchema.NegotiateEdrRequestSchema.NEGOTIATE_EDR_REQUEST_EXAMPLE; + +public class EdrSchema { + + @Schema(name = "NegotiateEdrRequest", example = NEGOTIATE_EDR_REQUEST_EXAMPLE) + public record NegotiateEdrRequestSchema( + @Schema(name = TYPE, example = EDR_REQUEST_DTO_TYPE) + String type, + String protocol, + String connectorAddress, + @Deprecated(since = "0.1.3") + @Schema(deprecated = true, description = "please use providerId instead") + String connectorId, + String providerId, + ContractNegotiationApi.ContractOfferDescriptionSchema offer, + List callbackAddresses) { + + public static final String NEGOTIATE_EDR_REQUEST_EXAMPLE = """ + { + "@context": { "edc": "https://w3id.org/edc/v0.0.1/ns/" }, + "@type": "NegotiateEdrRequestDto", + "connectorAddress": "http://provider-address", + "protocol": "dataspace-protocol-http", + "providerId": "provider-id", + "offer": { + "offerId": "offer-id", + "assetId": "asset-id", + "policy": { + "@context": "http://www.w3.org/ns/odrl.jsonld", + "@type": "Set", + "@id": "offer-id", + "permission": [{ + "target": "asset-id", + "action": "display" + }] + } + }, + "callbackAddresses": [{ + "transactional": false, + "uri": "http://callback/url", + "events": ["contract.negotiation", "transfer.process"] + }] + } + """; + } + + @Schema(name = "EndpointDataReferenceEntry", example = ENDPOINT_DATA_REFERENCE_ENTRY_EXAMPLE) + public record EndpointDataReferenceEntrySchema( + @Schema(name = TYPE, example = EndpointDataReferenceEntry.SIMPLE_TYPE) + String type, + String agreementId, + String assetId, + String providerId, + String edrState, + Long expirationDate + ) { + public static final String ENDPOINT_DATA_REFERENCE_ENTRY_EXAMPLE = """ + { + "@type": "tx:EndpointDataReferenceEntry", + "edc:agreementId": "MQ==:MQ==:ZTY3MzQ4YWEtNTdmZC00YzA0LTg2ZmQtMGMxNzk0MWM3OTkw", + "edc:transferProcessId": "78a66945-d638-4c0a-be71-b35a0318a410", + "edc:assetId": "1", + "edc:providerId": "BPNL00DATAP00001", + "tx:edrState": "NEGOTIATED", + "tx:expirationDate": 1690811364000, + "@context": { + "dct": "https://purl.org/dc/terms/", + "tx": "https://w3id.org/tractusx/v0.0.1/ns/", + "edc": "https://w3id.org/edc/v0.0.1/ns/", + "dcat": "https://www.w3.org/ns/dcat/", + "odrl": "http://www.w3.org/ns/odrl/2/", + "dspace": "https://w3id.org/dspace/v0.8/" + } + } + """; + } + +} diff --git a/edc-extensions/edr/edr-api/src/test/java/org/eclipse/tractusx/edc/api/edr/EdrApiTest.java b/edc-extensions/edr/edr-api/src/test/java/org/eclipse/tractusx/edc/api/edr/EdrApiTest.java new file mode 100644 index 000000000..9808bcac9 --- /dev/null +++ b/edc-extensions/edr/edr-api/src/test/java/org/eclipse/tractusx/edc/api/edr/EdrApiTest.java @@ -0,0 +1,117 @@ +/* + * Copyright (c) 2023 Bayerische Motoren Werke Aktiengesellschaft (BMW AG) + * + * 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 + * + * SPDX-License-Identifier: Apache-2.0 + * + * Contributors: + * Bayerische Motoren Werke Aktiengesellschaft (BMW AG) - initial API and implementation + * + */ + +package org.eclipse.tractusx.edc.api.edr; + +import com.fasterxml.jackson.core.JsonProcessingException; +import com.fasterxml.jackson.databind.ObjectMapper; +import jakarta.json.JsonObject; +import org.eclipse.edc.api.transformer.JsonObjectToCallbackAddressTransformer; +import org.eclipse.edc.connector.api.management.contractnegotiation.transform.JsonObjectToContractOfferDescriptionTransformer; +import org.eclipse.edc.connector.api.management.contractnegotiation.transform.JsonObjectToContractRequestTransformer; +import org.eclipse.edc.core.transform.TypeTransformerRegistryImpl; +import org.eclipse.edc.core.transform.transformer.OdrlTransformersFactory; +import org.eclipse.edc.jsonld.JsonLdExtension; +import org.eclipse.edc.jsonld.spi.JsonLd; +import org.eclipse.edc.jsonld.util.JacksonJsonLd; +import org.eclipse.edc.junit.assertions.AbstractResultAssert; +import org.eclipse.edc.transform.spi.TypeTransformerRegistry; +import org.eclipse.tractusx.edc.api.edr.dto.NegotiateEdrRequestDto; +import org.eclipse.tractusx.edc.api.edr.transform.JsonObjectToNegotiateEdrRequestDtoTransformer; +import org.eclipse.tractusx.edc.api.edr.validation.NegotiateEdrRequestDtoValidator; +import org.junit.jupiter.api.BeforeEach; +import org.junit.jupiter.api.Test; + +import static org.assertj.core.api.AssertionsForClassTypes.assertThat; +import static org.eclipse.edc.jsonld.spi.JsonLdKeywords.VALUE; +import static org.eclipse.edc.junit.extensions.TestServiceExtensionContext.testServiceExtensionContext; +import static org.eclipse.tractusx.edc.api.edr.schema.EdrSchema.EndpointDataReferenceEntrySchema.ENDPOINT_DATA_REFERENCE_ENTRY_EXAMPLE; +import static org.eclipse.tractusx.edc.api.edr.schema.EdrSchema.NegotiateEdrRequestSchema.NEGOTIATE_EDR_REQUEST_EXAMPLE; +import static org.eclipse.tractusx.edc.edr.spi.types.EndpointDataReferenceEntry.EDR_ENTRY_AGREEMENT_ID; +import static org.eclipse.tractusx.edc.edr.spi.types.EndpointDataReferenceEntry.EDR_ENTRY_ASSET_ID; +import static org.eclipse.tractusx.edc.edr.spi.types.EndpointDataReferenceEntry.EDR_ENTRY_EXPIRATION_DATE; +import static org.eclipse.tractusx.edc.edr.spi.types.EndpointDataReferenceEntry.EDR_ENTRY_PROVIDER_ID; +import static org.eclipse.tractusx.edc.edr.spi.types.EndpointDataReferenceEntry.EDR_ENTRY_STATE; +import static org.eclipse.tractusx.edc.edr.spi.types.EndpointDataReferenceEntry.EDR_ENTRY_TRANSFER_PROCESS_ID; + +public class EdrApiTest { + + private final ObjectMapper objectMapper = JacksonJsonLd.createObjectMapper(); + private final JsonLd jsonLd = new JsonLdExtension().createJsonLdService(testServiceExtensionContext()); + + private final TypeTransformerRegistry transformer = new TypeTransformerRegistryImpl(); + + + @BeforeEach + void setUp() { + transformer.register(new JsonObjectToContractRequestTransformer()); + transformer.register(new JsonObjectToContractOfferDescriptionTransformer()); + transformer.register(new JsonObjectToCallbackAddressTransformer()); + transformer.register(new JsonObjectToNegotiateEdrRequestDtoTransformer()); + OdrlTransformersFactory.jsonObjectToOdrlTransformers().forEach(transformer::register); + } + + @Test + void edrRequestExample() throws JsonProcessingException { + var validator = NegotiateEdrRequestDtoValidator.instance(); + + var jsonObject = objectMapper.readValue(NEGOTIATE_EDR_REQUEST_EXAMPLE, JsonObject.class); + assertThat(jsonObject).isNotNull(); + + var expanded = jsonLd.expand(jsonObject); + AbstractResultAssert.assertThat(expanded).isSucceeded() + .satisfies(exp -> AbstractResultAssert.assertThat(validator.validate(exp)).isSucceeded()) + .extracting(e -> transformer.transform(e, NegotiateEdrRequestDto.class)) + .satisfies(transformResult -> AbstractResultAssert.assertThat(transformResult).isSucceeded() + .satisfies(transformed -> { + assertThat(transformed.getOffer()).isNotNull(); + assertThat(transformed.getCallbackAddresses()).asList().hasSize(1); + assertThat(transformed.getProviderId()).isNotBlank(); + })); + } + + @Test + void edrEntryExample() throws JsonProcessingException { + + var jsonObject = objectMapper.readValue(ENDPOINT_DATA_REFERENCE_ENTRY_EXAMPLE, JsonObject.class); + assertThat(jsonObject).isNotNull(); + + var expanded = jsonLd.expand(jsonObject); + + AbstractResultAssert.assertThat(expanded).isSucceeded().satisfies(content -> { + + assertThat(first(content, EDR_ENTRY_STATE).getJsonString(VALUE).getString()) + .isEqualTo(jsonObject.getString("tx:edrState")); + + assertThat(first(content, EDR_ENTRY_ASSET_ID).getJsonString(VALUE).getString()) + .isEqualTo(jsonObject.getString("edc:assetId")); + + assertThat(first(content, EDR_ENTRY_AGREEMENT_ID).getJsonString(VALUE).getString()) + .isEqualTo(jsonObject.getString("edc:agreementId")); + + assertThat(first(content, EDR_ENTRY_TRANSFER_PROCESS_ID).getJsonString(VALUE).getString()) + .isEqualTo(jsonObject.getString("edc:transferProcessId")); + + assertThat(first(content, EDR_ENTRY_PROVIDER_ID).getJsonString(VALUE).getString()) + .isEqualTo(jsonObject.getString("edc:providerId")); + + assertThat(first(content, EDR_ENTRY_EXPIRATION_DATE).getJsonNumber(VALUE).longValue()) + .isEqualTo(jsonObject.getJsonNumber("tx:expirationDate").longValue()); + }); + } + + private JsonObject first(JsonObject content, String name) { + return content.getJsonArray(name).getJsonObject(0); + } +} diff --git a/gradle/libs.versions.toml b/gradle/libs.versions.toml index 9d6e40bc8..8c75dd2ba 100644 --- a/gradle/libs.versions.toml +++ b/gradle/libs.versions.toml @@ -56,6 +56,7 @@ edc-core-jersey = { module = "org.eclipse.edc:jersey-core", version.ref = "edc" edc-core-api = { module = "org.eclipse.edc:api-core", version.ref = "edc" } edc-core-sql = { module = "org.eclipse.edc:sql-core", version.ref = "edc" } edc-core-validator = { module = "org.eclipse.edc:validator-core", version.ref = "edc" } +edc-core-transform = { module = "org.eclipse.edc:transform-core", version.ref = "edc" } edc-statemachine = { module = "org.eclipse.edc:state-machine", version.ref = "edc" } edc-junit = { module = "org.eclipse.edc:junit", version.ref = "edc" } edc-api-management-config = { module = "org.eclipse.edc:management-api-configuration", version.ref = "edc" }