From 9754e5d236220ab8567705c252c851a97b396058 Mon Sep 17 00:00:00 2001 From: Richard Treier Date: Fri, 13 Oct 2023 11:14:11 +0200 Subject: [PATCH] feat: handle custom transfer process properties in the backend (#565) --- .../edc/ext/wrapper/api/ui/UiResource.java | 12 ++- .../ContractAgreementTransferRequestType.java | 23 ------ ...ava => InitiateCustomTransferRequest.java} | 13 ++- ...rams.java => InitiateTransferRequest.java} | 2 +- .../WrapperExtensionContextBuilder.java | 3 +- .../ext/wrapper/api/ui/UiResourceImpl.java | 16 ++-- .../ContractAgreementTransferApiService.java | 21 +++-- .../services/TransferRequestBuilder.java | 79 ++++++++++++------ ...ntractAgreementTransferApiServiceTest.java | 67 ++++++++++++--- .../de/sovity/edc/e2e/UiApiWrapperTest.java | 82 +++++++++++++++++-- .../sovity/edc/utils/jsonld/JsonLdUtils.java | 17 ++++ .../sovity/edc/utils/jsonld/vocab/Prop.java | 10 +++ 12 files changed, 248 insertions(+), 97 deletions(-) delete mode 100644 extensions/wrapper/wrapper-api/src/main/java/de/sovity/edc/ext/wrapper/api/ui/model/ContractAgreementTransferRequestType.java rename extensions/wrapper/wrapper-api/src/main/java/de/sovity/edc/ext/wrapper/api/ui/model/{ContractAgreementTransferRequest.java => InitiateCustomTransferRequest.java} (61%) rename extensions/wrapper/wrapper-api/src/main/java/de/sovity/edc/ext/wrapper/api/ui/model/{ContractAgreementTransferRequestParams.java => InitiateTransferRequest.java} (95%) diff --git a/extensions/wrapper/wrapper-api/src/main/java/de/sovity/edc/ext/wrapper/api/ui/UiResource.java b/extensions/wrapper/wrapper-api/src/main/java/de/sovity/edc/ext/wrapper/api/ui/UiResource.java index c18509a86..aada52c6a 100644 --- a/extensions/wrapper/wrapper-api/src/main/java/de/sovity/edc/ext/wrapper/api/ui/UiResource.java +++ b/extensions/wrapper/wrapper-api/src/main/java/de/sovity/edc/ext/wrapper/api/ui/UiResource.java @@ -19,12 +19,13 @@ import de.sovity.edc.ext.wrapper.api.common.model.UiAssetCreateRequest; import de.sovity.edc.ext.wrapper.api.ui.model.AssetPage; import de.sovity.edc.ext.wrapper.api.ui.model.ContractAgreementPage; -import de.sovity.edc.ext.wrapper.api.ui.model.ContractAgreementTransferRequest; import de.sovity.edc.ext.wrapper.api.ui.model.ContractDefinitionPage; import de.sovity.edc.ext.wrapper.api.ui.model.ContractDefinitionRequest; import de.sovity.edc.ext.wrapper.api.ui.model.ContractNegotiationRequest; import de.sovity.edc.ext.wrapper.api.ui.model.DashboardPage; import de.sovity.edc.ext.wrapper.api.ui.model.IdResponseDto; +import de.sovity.edc.ext.wrapper.api.ui.model.InitiateCustomTransferRequest; +import de.sovity.edc.ext.wrapper.api.ui.model.InitiateTransferRequest; import de.sovity.edc.ext.wrapper.api.ui.model.PolicyDefinitionPage; import de.sovity.edc.ext.wrapper.api.ui.model.TransferHistoryPage; import de.sovity.edc.ext.wrapper.api.ui.model.UiContractNegotiation; @@ -140,7 +141,14 @@ interface UiResource { @Consumes(MediaType.APPLICATION_JSON) @Produces(MediaType.APPLICATION_JSON) @Operation(description = "Initiate a Transfer Process") - IdResponseDto initiateTransfer(ContractAgreementTransferRequest contractAgreementTransferRequest); + IdResponseDto initiateTransfer(InitiateTransferRequest initiateTransferRequest); + + @POST + @Path("pages/contract-agreement-page/transfers/custom") + @Consumes(MediaType.APPLICATION_JSON) + @Produces(MediaType.APPLICATION_JSON) + @Operation(description = "Initiate a Transfer Process via a custom Transfer Process JSON-LD. Fields such as connectorId, assetId, providerConnectorId, providerConnectorAddress will be set automatically.") + IdResponseDto initiateCustomTransfer(InitiateCustomTransferRequest initiateCustomTransferRequest); @GET @Path("pages/transfer-history-page") diff --git a/extensions/wrapper/wrapper-api/src/main/java/de/sovity/edc/ext/wrapper/api/ui/model/ContractAgreementTransferRequestType.java b/extensions/wrapper/wrapper-api/src/main/java/de/sovity/edc/ext/wrapper/api/ui/model/ContractAgreementTransferRequestType.java deleted file mode 100644 index 876006885..000000000 --- a/extensions/wrapper/wrapper-api/src/main/java/de/sovity/edc/ext/wrapper/api/ui/model/ContractAgreementTransferRequestType.java +++ /dev/null @@ -1,23 +0,0 @@ -/* - * Copyright (c) 2022 sovity GmbH - * - * 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: - * sovity GmbH - initial API and implementation - * - */ - -package de.sovity.edc.ext.wrapper.api.ui.model; - -import io.swagger.v3.oas.annotations.media.Schema; - -@Schema(description = "Type of Transfer Request", enumAsRef = true) -public enum ContractAgreementTransferRequestType { - PARAMS_ONLY, - CUSTOM_JSON -} diff --git a/extensions/wrapper/wrapper-api/src/main/java/de/sovity/edc/ext/wrapper/api/ui/model/ContractAgreementTransferRequest.java b/extensions/wrapper/wrapper-api/src/main/java/de/sovity/edc/ext/wrapper/api/ui/model/InitiateCustomTransferRequest.java similarity index 61% rename from extensions/wrapper/wrapper-api/src/main/java/de/sovity/edc/ext/wrapper/api/ui/model/ContractAgreementTransferRequest.java rename to extensions/wrapper/wrapper-api/src/main/java/de/sovity/edc/ext/wrapper/api/ui/model/InitiateCustomTransferRequest.java index 5f9eeb05e..31989ada4 100644 --- a/extensions/wrapper/wrapper-api/src/main/java/de/sovity/edc/ext/wrapper/api/ui/model/ContractAgreementTransferRequest.java +++ b/extensions/wrapper/wrapper-api/src/main/java/de/sovity/edc/ext/wrapper/api/ui/model/InitiateCustomTransferRequest.java @@ -27,13 +27,10 @@ @AllArgsConstructor @RequiredArgsConstructor @Schema(description = "Required data for starting a Contract Agreement's Transfer Process") -public class ContractAgreementTransferRequest { - @Schema(description = "Type of request", requiredMode = Schema.RequiredMode.REQUIRED) - private ContractAgreementTransferRequestType type; +public class InitiateCustomTransferRequest { + @Schema(description = "Contract Agreement ID", requiredMode = Schema.RequiredMode.REQUIRED) + private String contractAgreementId; - @Schema(description = "For type PARAMS_ONLY: Required data for starting a Transfer Process") - private ContractAgreementTransferRequestParams params; - - @Schema(description = "For type CUSTOM_JSON: Custom Transfer Process Create Dto JSON") - private String customJson; + @Schema(description = "Partial TransferProcessRequestJsonLd JSON-LD. Fields participantId, connectorEndpoint, assetId and contractId can be omitted, they will be overridden with information from the contract.") + private String transferProcessRequestJsonLd; } diff --git a/extensions/wrapper/wrapper-api/src/main/java/de/sovity/edc/ext/wrapper/api/ui/model/ContractAgreementTransferRequestParams.java b/extensions/wrapper/wrapper-api/src/main/java/de/sovity/edc/ext/wrapper/api/ui/model/InitiateTransferRequest.java similarity index 95% rename from extensions/wrapper/wrapper-api/src/main/java/de/sovity/edc/ext/wrapper/api/ui/model/ContractAgreementTransferRequestParams.java rename to extensions/wrapper/wrapper-api/src/main/java/de/sovity/edc/ext/wrapper/api/ui/model/InitiateTransferRequest.java index 78f9515c5..634ba0ddc 100644 --- a/extensions/wrapper/wrapper-api/src/main/java/de/sovity/edc/ext/wrapper/api/ui/model/ContractAgreementTransferRequestParams.java +++ b/extensions/wrapper/wrapper-api/src/main/java/de/sovity/edc/ext/wrapper/api/ui/model/InitiateTransferRequest.java @@ -29,7 +29,7 @@ @AllArgsConstructor @RequiredArgsConstructor @Schema(description = "For type PARAMS_ONLY: Required data for starting a Transfer Process") -public class ContractAgreementTransferRequestParams { +public class InitiateTransferRequest { @Schema(description = "Contract Agreement ID", requiredMode = Schema.RequiredMode.REQUIRED) private String contractAgreementId; diff --git a/extensions/wrapper/wrapper/src/main/java/de/sovity/edc/ext/wrapper/WrapperExtensionContextBuilder.java b/extensions/wrapper/wrapper/src/main/java/de/sovity/edc/ext/wrapper/WrapperExtensionContextBuilder.java index 32eda5b85..587b4d22d 100644 --- a/extensions/wrapper/wrapper/src/main/java/de/sovity/edc/ext/wrapper/WrapperExtensionContextBuilder.java +++ b/extensions/wrapper/wrapper/src/main/java/de/sovity/edc/ext/wrapper/WrapperExtensionContextBuilder.java @@ -180,11 +180,10 @@ public static WrapperExtensionContext buildContext( selfDescriptionService ); var transferRequestBuilder = new TransferRequestBuilder( - objectMapper, contractAgreementUtils, contractNegotiationUtils, edcPropertyUtils, - serviceExtensionContext.getConnectorId() + typeTransformerRegistry ); var contractAgreementTransferApiService = new ContractAgreementTransferApiService( transferRequestBuilder, diff --git a/extensions/wrapper/wrapper/src/main/java/de/sovity/edc/ext/wrapper/api/ui/UiResourceImpl.java b/extensions/wrapper/wrapper/src/main/java/de/sovity/edc/ext/wrapper/api/ui/UiResourceImpl.java index a166b4263..b809d4b29 100644 --- a/extensions/wrapper/wrapper/src/main/java/de/sovity/edc/ext/wrapper/api/ui/UiResourceImpl.java +++ b/extensions/wrapper/wrapper/src/main/java/de/sovity/edc/ext/wrapper/api/ui/UiResourceImpl.java @@ -19,12 +19,13 @@ import de.sovity.edc.ext.wrapper.api.common.model.UiAssetCreateRequest; import de.sovity.edc.ext.wrapper.api.ui.model.AssetPage; import de.sovity.edc.ext.wrapper.api.ui.model.ContractAgreementPage; -import de.sovity.edc.ext.wrapper.api.ui.model.ContractAgreementTransferRequest; +import de.sovity.edc.ext.wrapper.api.ui.model.InitiateCustomTransferRequest; import de.sovity.edc.ext.wrapper.api.ui.model.ContractDefinitionPage; import de.sovity.edc.ext.wrapper.api.ui.model.ContractDefinitionRequest; import de.sovity.edc.ext.wrapper.api.ui.model.ContractNegotiationRequest; import de.sovity.edc.ext.wrapper.api.ui.model.DashboardPage; import de.sovity.edc.ext.wrapper.api.ui.model.IdResponseDto; +import de.sovity.edc.ext.wrapper.api.ui.model.InitiateTransferRequest; import de.sovity.edc.ext.wrapper.api.ui.model.PolicyDefinitionPage; import de.sovity.edc.ext.wrapper.api.ui.model.TransferHistoryPage; import de.sovity.edc.ext.wrapper.api.ui.model.UiContractNegotiation; @@ -129,12 +130,13 @@ public ContractAgreementPage getContractAgreementPage() { } @Override - public IdResponseDto initiateTransfer( - ContractAgreementTransferRequest contractAgreementTransferRequest - ) { - return contractAgreementTransferApiService.initiateTransfer( - contractAgreementTransferRequest - ); + public IdResponseDto initiateTransfer(InitiateTransferRequest request) { + return contractAgreementTransferApiService.initiateTransfer(request); + } + + @Override + public IdResponseDto initiateCustomTransfer(InitiateCustomTransferRequest request) { + return contractAgreementTransferApiService.initiateCustomTransfer(request); } @Override diff --git a/extensions/wrapper/wrapper/src/main/java/de/sovity/edc/ext/wrapper/api/ui/pages/contract_agreements/ContractAgreementTransferApiService.java b/extensions/wrapper/wrapper/src/main/java/de/sovity/edc/ext/wrapper/api/ui/pages/contract_agreements/ContractAgreementTransferApiService.java index a85801751..3f49bd642 100644 --- a/extensions/wrapper/wrapper/src/main/java/de/sovity/edc/ext/wrapper/api/ui/pages/contract_agreements/ContractAgreementTransferApiService.java +++ b/extensions/wrapper/wrapper/src/main/java/de/sovity/edc/ext/wrapper/api/ui/pages/contract_agreements/ContractAgreementTransferApiService.java @@ -14,12 +14,14 @@ package de.sovity.edc.ext.wrapper.api.ui.pages.contract_agreements; -import de.sovity.edc.ext.wrapper.api.ui.model.ContractAgreementTransferRequest; +import de.sovity.edc.ext.wrapper.api.ui.model.InitiateCustomTransferRequest; import de.sovity.edc.ext.wrapper.api.ui.model.IdResponseDto; +import de.sovity.edc.ext.wrapper.api.ui.model.InitiateTransferRequest; import de.sovity.edc.ext.wrapper.api.ui.pages.contract_agreements.services.TransferRequestBuilder; import lombok.RequiredArgsConstructor; import org.eclipse.edc.connector.spi.transferprocess.TransferProcessService; import org.eclipse.edc.connector.transfer.spi.types.TransferProcess; +import org.eclipse.edc.connector.transfer.spi.types.TransferRequest; import org.jetbrains.annotations.NotNull; import static org.eclipse.edc.web.spi.exception.ServiceResultHandler.exceptionMapper; @@ -30,10 +32,19 @@ public class ContractAgreementTransferApiService { private final TransferProcessService transferProcessService; @NotNull - public IdResponseDto initiateTransfer( - ContractAgreementTransferRequest request - ) { - var transferRequest = transferRequestBuilder.buildTransferRequest(request); + public IdResponseDto initiateTransfer(InitiateTransferRequest request) { + var transferRequest = transferRequestBuilder.buildCustomTransferRequest(request); + return initiate(transferRequest); + } + + @NotNull + public IdResponseDto initiateCustomTransfer(InitiateCustomTransferRequest request) { + var transferRequest = transferRequestBuilder.buildCustomTransferRequest(request); + return initiate(transferRequest); + } + + @NotNull + private IdResponseDto initiate(TransferRequest transferRequest) { var transferProcess = transferProcessService.initiateTransfer(transferRequest) .orElseThrow(exceptionMapper(TransferProcess.class, transferRequest.getId())); return new IdResponseDto(transferProcess.getId()); diff --git a/extensions/wrapper/wrapper/src/main/java/de/sovity/edc/ext/wrapper/api/ui/pages/contract_agreements/services/TransferRequestBuilder.java b/extensions/wrapper/wrapper/src/main/java/de/sovity/edc/ext/wrapper/api/ui/pages/contract_agreements/services/TransferRequestBuilder.java index a4982a344..404be6b4d 100644 --- a/extensions/wrapper/wrapper/src/main/java/de/sovity/edc/ext/wrapper/api/ui/pages/contract_agreements/services/TransferRequestBuilder.java +++ b/extensions/wrapper/wrapper/src/main/java/de/sovity/edc/ext/wrapper/api/ui/pages/contract_agreements/services/TransferRequestBuilder.java @@ -14,62 +14,87 @@ package de.sovity.edc.ext.wrapper.api.ui.pages.contract_agreements.services; -import com.fasterxml.jackson.databind.ObjectMapper; +import de.sovity.edc.ext.wrapper.api.ServiceException; import de.sovity.edc.ext.wrapper.api.common.mappers.utils.EdcPropertyUtils; -import de.sovity.edc.ext.wrapper.api.ui.model.ContractAgreementTransferRequest; -import de.sovity.edc.ext.wrapper.api.ui.model.ContractAgreementTransferRequestParams; +import de.sovity.edc.ext.wrapper.api.ui.model.InitiateCustomTransferRequest; +import de.sovity.edc.ext.wrapper.api.ui.model.InitiateTransferRequest; +import de.sovity.edc.utils.JsonUtils; +import de.sovity.edc.utils.jsonld.JsonLdUtils; +import de.sovity.edc.utils.jsonld.vocab.Prop; +import jakarta.json.Json; import lombok.RequiredArgsConstructor; -import lombok.SneakyThrows; +import org.apache.commons.lang3.Validate; +import org.eclipse.edc.connector.contract.spi.types.negotiation.ContractNegotiation; import org.eclipse.edc.connector.transfer.spi.types.TransferRequest; import org.eclipse.edc.protocol.dsp.spi.types.HttpMessageProtocol; +import org.eclipse.edc.transform.spi.TypeTransformerRegistry; import java.util.List; -import java.util.Objects; import java.util.UUID; @RequiredArgsConstructor public class TransferRequestBuilder { - - private final ObjectMapper objectMapper; private final ContractAgreementUtils contractAgreementUtils; private final ContractNegotiationUtils contractNegotiationUtils; private final EdcPropertyUtils edcPropertyUtils; - private final String connectorId; - - public TransferRequest buildTransferRequest( - ContractAgreementTransferRequest request - ) { - Objects.requireNonNull(request.getType(), "type"); - return switch (request.getType()) { - case PARAMS_ONLY -> buildTransferRequest(request.getParams()); - case CUSTOM_JSON -> parseTransferRequestJson(request); - }; - } + private final TypeTransformerRegistry typeTransformerRegistry; - private TransferRequest buildTransferRequest( - ContractAgreementTransferRequestParams params + public TransferRequest buildCustomTransferRequest( + InitiateTransferRequest request ) { - var contractId = params.getContractAgreementId(); + var contractId = request.getContractAgreementId(); var agreement = contractAgreementUtils.findByIdOrThrow(contractId); var negotiation = contractNegotiationUtils.findByContractAgreementIdOrThrow(contractId); - var address = edcPropertyUtils.buildDataAddress(params.getDataSinkProperties()); + var address = edcPropertyUtils.buildDataAddress(request.getDataSinkProperties()); + assertIsConsuming(negotiation); return TransferRequest.Builder.newInstance() .id(UUID.randomUUID().toString()) .protocol(HttpMessageProtocol.DATASPACE_PROTOCOL_HTTP) .connectorAddress(negotiation.getCounterPartyAddress()) - .connectorId(connectorId) + .connectorId(negotiation.getCounterPartyId()) .contractId(contractId) .assetId(agreement.getAssetId()) .dataDestination(address) - .privateProperties(edcPropertyUtils.toMapOfObject(params.getTransferProcessProperties())) + .privateProperties(edcPropertyUtils.toMapOfObject(request.getTransferProcessProperties())) .callbackAddresses(List.of()) .build(); } - @SneakyThrows - private TransferRequest parseTransferRequestJson(ContractAgreementTransferRequest request) { - return objectMapper.readValue(request.getCustomJson(), TransferRequest.class); + public TransferRequest buildCustomTransferRequest( + InitiateCustomTransferRequest request + ) { + var contractId = request.getContractAgreementId(); + var agreement = contractAgreementUtils.findByIdOrThrow(contractId); + var negotiation = contractNegotiationUtils.findByContractAgreementIdOrThrow(contractId); + assertIsConsuming(negotiation); + + // Parse Transfer Process JSON-LD + var requestJsonLd = JsonUtils.parseJsonObj(request.getTransferProcessRequestJsonLd()); + + // Expand JSON-LD Property names + requestJsonLd = Json.createObjectBuilder(requestJsonLd) + .add(Prop.TYPE, Prop.Edc.TYPE_TRANSFER_REQUEST) + .add(Prop.CONTEXT, Json.createObjectBuilder(JsonLdUtils.object(requestJsonLd, Prop.CONTEXT)) + .add(Prop.Edc.CTX_ALIAS, Prop.Edc.CTX)) + .build(); + requestJsonLd = JsonLdUtils.expandKeysOnly(requestJsonLd); + + // Add missing properties + requestJsonLd = Json.createObjectBuilder(requestJsonLd) + .add(Prop.TYPE, Prop.Edc.TYPE_TRANSFER_REQUEST) + .add(Prop.Edc.ASSET_ID, agreement.getAssetId()) + .add(Prop.Edc.CONTRACT_ID, agreement.getId()) + .add(Prop.Edc.CONNECTOR_ID, negotiation.getCounterPartyId()) + .add(Prop.Edc.CONNECTOR_ADDRESS, negotiation.getCounterPartyAddress()) + .build(); + + return typeTransformerRegistry.transform(requestJsonLd, TransferRequest.class) + .orElseThrow(ServiceException::new); } + private void assertIsConsuming(ContractNegotiation negotiation) { + Validate.isTrue(negotiation.getType() == ContractNegotiation.Type.CONSUMER, + "Agreement is not a consuming agreement."); + } } diff --git a/extensions/wrapper/wrapper/src/test/java/de/sovity/edc/ext/wrapper/api/ui/pages/contract_agreement/ContractAgreementTransferApiServiceTest.java b/extensions/wrapper/wrapper/src/test/java/de/sovity/edc/ext/wrapper/api/ui/pages/contract_agreement/ContractAgreementTransferApiServiceTest.java index ae1b118da..284b375ce 100644 --- a/extensions/wrapper/wrapper/src/test/java/de/sovity/edc/ext/wrapper/api/ui/pages/contract_agreement/ContractAgreementTransferApiServiceTest.java +++ b/extensions/wrapper/wrapper/src/test/java/de/sovity/edc/ext/wrapper/api/ui/pages/contract_agreement/ContractAgreementTransferApiServiceTest.java @@ -15,10 +15,12 @@ package de.sovity.edc.ext.wrapper.api.ui.pages.contract_agreement; import de.sovity.edc.client.EdcClient; -import de.sovity.edc.client.gen.model.ContractAgreementTransferRequest; -import de.sovity.edc.client.gen.model.ContractAgreementTransferRequestParams; -import de.sovity.edc.client.gen.model.ContractAgreementTransferRequestType; +import de.sovity.edc.client.gen.model.InitiateCustomTransferRequest; +import de.sovity.edc.client.gen.model.InitiateTransferRequest; import de.sovity.edc.ext.wrapper.TestUtils; +import de.sovity.edc.utils.JsonUtils; +import de.sovity.edc.utils.jsonld.vocab.Prop; +import jakarta.json.Json; import org.eclipse.edc.connector.contract.spi.negotiation.store.ContractNegotiationStore; import org.eclipse.edc.connector.contract.spi.types.agreement.ContractAgreement; import org.eclipse.edc.connector.contract.spi.types.negotiation.ContractNegotiation; @@ -63,17 +65,13 @@ void startTransferProcessForAgreementId( var contractId = UUID.randomUUID().toString(); createContractNegotiation(store, COUNTER_PARTY_ADDRESS, contractId); - var request = new ContractAgreementTransferRequest( - ContractAgreementTransferRequestType.PARAMS_ONLY, - new ContractAgreementTransferRequestParams( - contractId, - Map.of( - "type", "HttpData", - "baseUrl", DATA_SINK - ), - Map.of("privateProperty", "privateValue") + var request = new InitiateTransferRequest( + contractId, + Map.of( + "type", "HttpData", + "baseUrl", DATA_SINK ), - null + Map.of("privateProperty", "privateValue") ); // act @@ -95,6 +93,49 @@ void startTransferProcessForAgreementId( )); } + @Test + void startCustomTransferProcessForAgreementId( + ContractNegotiationStore store, + TransferProcessStore transferProcessStore + ) { + // arrange + var contractId = UUID.randomUUID().toString(); + createContractNegotiation(store, COUNTER_PARTY_ADDRESS, contractId); + + var customRequestJson = Json.createObjectBuilder() + .add(Prop.Edc.DATA_DESTINATION, Json.createObjectBuilder() + .add(Prop.Edc.TYPE, "HttpData") + .add(Prop.Edc.BASE_URL, DATA_SINK)) + .add(Prop.Edc.PRIVATE_PROPERTIES, Json.createObjectBuilder() + .add(Prop.Edc.RECEIVER_HTTP_ENDPOINT, "http://my-pull-backend") + .add("this-will-disappear", "because-its-not-an-url") + .add("http://unknown/custom-prop", "value")) + .build(); + var request = new InitiateCustomTransferRequest( + contractId, + JsonUtils.toJson(customRequestJson) + ); + + // act + var result = client.uiApi().initiateCustomTransfer(request); + + // then + var transferProcess = transferProcessStore.findById(result.getId()); + assertThat(transferProcess).isNotNull(); + assertThat(transferProcess.getPrivateProperties()).containsAllEntriesOf(Map.of( + Prop.Edc.RECEIVER_HTTP_ENDPOINT, "http://my-pull-backend", + "http://unknown/custom-prop", "value" + )); + + var dataRequest = transferProcess.getDataRequest(); + assertThat(dataRequest.getContractId()).isEqualTo(contractId); + assertThat(dataRequest.getConnectorAddress()).isEqualTo(COUNTER_PARTY_ADDRESS); + assertThat(dataRequest.getDataDestination().getProperties()).containsAllEntriesOf(Map.of( + Prop.Edc.TYPE, "HttpData", + Prop.Edc.BASE_URL, DATA_SINK + )); + } + private ContractNegotiation createContractNegotiation( ContractNegotiationStore store, String counterPartyAddress, diff --git a/launchers/connectors/sovity-dev/src/test/java/de/sovity/edc/e2e/UiApiWrapperTest.java b/launchers/connectors/sovity-dev/src/test/java/de/sovity/edc/e2e/UiApiWrapperTest.java index ca825a3eb..3980f79e6 100644 --- a/launchers/connectors/sovity-dev/src/test/java/de/sovity/edc/e2e/UiApiWrapperTest.java +++ b/launchers/connectors/sovity-dev/src/test/java/de/sovity/edc/e2e/UiApiWrapperTest.java @@ -14,12 +14,11 @@ package de.sovity.edc.e2e; import de.sovity.edc.client.EdcClient; -import de.sovity.edc.client.gen.model.ContractAgreementTransferRequest; -import de.sovity.edc.client.gen.model.ContractAgreementTransferRequestParams; -import de.sovity.edc.client.gen.model.ContractAgreementTransferRequestType; import de.sovity.edc.client.gen.model.ContractDefinitionRequest; import de.sovity.edc.client.gen.model.ContractNegotiationRequest; import de.sovity.edc.client.gen.model.ContractNegotiationSimplifiedState; +import de.sovity.edc.client.gen.model.InitiateCustomTransferRequest; +import de.sovity.edc.client.gen.model.InitiateTransferRequest; import de.sovity.edc.client.gen.model.OperatorDto; import de.sovity.edc.client.gen.model.PolicyDefinitionCreateRequest; import de.sovity.edc.client.gen.model.TransferProcessSimplifiedState; @@ -39,9 +38,13 @@ import de.sovity.edc.extension.e2e.connector.MockDataAddressRemote; import de.sovity.edc.extension.e2e.db.TestDatabase; import de.sovity.edc.extension.e2e.db.TestDatabaseFactory; +import de.sovity.edc.utils.JsonUtils; import de.sovity.edc.utils.jsonld.vocab.Prop; +import jakarta.json.Json; +import jakarta.json.JsonObject; import org.awaitility.Awaitility; import org.eclipse.edc.junit.extensions.EdcExtension; +import org.eclipse.edc.protocol.dsp.spi.types.HttpMessageProtocol; import org.junit.jupiter.api.BeforeEach; import org.junit.jupiter.api.Test; import org.junit.jupiter.api.extension.RegisterExtension; @@ -282,6 +285,64 @@ void provide_consume_assetMapping_policyMapping_agreements() { assertThat(completedConsumingTransferProcess.getState().getSimplifiedState()).isEqualTo(TransferProcessSimplifiedState.RUNNING); } + @Test + void customTransferRequest() { + // arrange + var data = "expected data 123"; + + var assetId = providerClient.uiApi().createAsset(UiAssetCreateRequest.builder() + .id("asset-1") + .dataAddressProperties(Map.of( + Prop.Edc.TYPE, "HttpData", + Prop.Edc.METHOD, "GET", + Prop.Edc.BASE_URL, dataAddress.getDataSourceUrl(data) + )) + .build()).getId(); + assertThat(assetId).isEqualTo("asset-1"); + + var policyId = providerClient.uiApi().createPolicyDefinition(PolicyDefinitionCreateRequest.builder() + .policyDefinitionId("policy-1") + .policy(UiPolicyCreateRequest.builder() + .constraints(List.of()) + .build()) + .build()).getId(); + + providerClient.uiApi().createContractDefinition(ContractDefinitionRequest.builder() + .contractDefinitionId("cd-1") + .accessPolicyId(policyId) + .contractPolicyId(policyId) + .assetSelector(List.of()) + .build()); + + var dataOffers = consumerClient.uiApi().getCatalogPageDataOffers(getProtocolEndpoint(providerConnector)); + assertThat(dataOffers).hasSize(1); + var dataOffer = dataOffers.get(0); + assertThat(dataOffer.getContractOffers()).hasSize(1); + var contractOffer = dataOffer.getContractOffers().get(0); + + // act + var negotiation = negotiate(dataOffer, contractOffer); + var transferRequestJsonLd = Json.createObjectBuilder() + .add( + Prop.Edc.DATA_DESTINATION, + getDatasinkPropertiesJsonObject() + ) + .add(Prop.Edc.CTX + "transferType", Json.createObjectBuilder() + .add(Prop.Edc.CTX + "contentType", "application/octet-stream") + .add(Prop.Edc.CTX + "isFinite", true) + ) + .add(Prop.Edc.CTX + "protocol", HttpMessageProtocol.DATASPACE_PROTOCOL_HTTP) + .add(Prop.Edc.CTX + "managedResources", false) + .build(); + var transferRequest = InitiateCustomTransferRequest.builder() + .contractAgreementId(negotiation.getContractAgreementId()) + .transferProcessRequestJsonLd(JsonUtils.toJson(transferRequestJsonLd)) + .build(); + consumerClient.uiApi().initiateCustomTransfer(transferRequest); + + validateDataTransferred(dataAddress.getDataSinkSpyUrl(), data); + } + private UiContractNegotiation negotiate(UiDataOffer dataOffer, UiContractOffer contractOffer) { var negotiationRequest = ContractNegotiationRequest.builder() .counterPartyAddress(dataOffer.getEndpoint()) @@ -305,16 +366,19 @@ private UiContractNegotiation negotiate(UiDataOffer dataOffer, UiContractOffer c private void initiateTransfer(UiContractNegotiation negotiation) { var contractAgreementId = negotiation.getContractAgreementId(); - var transferRequest = ContractAgreementTransferRequest.builder() - .type(ContractAgreementTransferRequestType.PARAMS_ONLY) - .params(ContractAgreementTransferRequestParams.builder() - .contractAgreementId(contractAgreementId) - .dataSinkProperties(dataAddress.getDataSinkProperties()) - .build()) + var transferRequest = InitiateTransferRequest.builder() + .contractAgreementId(contractAgreementId) + .dataSinkProperties(dataAddress.getDataSinkProperties()) .build(); consumerClient.uiApi().initiateTransfer(transferRequest); } + @SuppressWarnings({"unchecked", "rawtypes"}) + private JsonObject getDatasinkPropertiesJsonObject() { + var props = dataAddress.getDataSinkProperties(); + return Json.createObjectBuilder((Map) (Map) props).build(); + } + private String getProtocolEndpoint(ConnectorRemote connector) { return connector.getConfig().getProtocolEndpoint().getUri().toString(); } diff --git a/utils/json-and-jsonld-utils/src/main/java/de/sovity/edc/utils/jsonld/JsonLdUtils.java b/utils/json-and-jsonld-utils/src/main/java/de/sovity/edc/utils/jsonld/JsonLdUtils.java index 9103afd4c..bbe648507 100644 --- a/utils/json-and-jsonld-utils/src/main/java/de/sovity/edc/utils/jsonld/JsonLdUtils.java +++ b/utils/json-and-jsonld-utils/src/main/java/de/sovity/edc/utils/jsonld/JsonLdUtils.java @@ -18,9 +18,11 @@ import de.sovity.edc.utils.JsonUtils; import de.sovity.edc.utils.jsonld.vocab.Prop; import jakarta.json.Json; +import jakarta.json.JsonArray; import jakarta.json.JsonNumber; import jakarta.json.JsonObject; import jakarta.json.JsonString; +import jakarta.json.JsonStructure; import jakarta.json.JsonValue; import lombok.AccessLevel; import lombok.NoArgsConstructor; @@ -47,6 +49,21 @@ public static JsonObject tryCompact(JsonObject json) { } } + /** + * Compact JSON-LD, but don't compact property names to namespaces. + * + * @param json json-ld + * @return compacted values + */ + public static JsonObject expandKeysOnly(JsonObject json) { + try { + var expanded = com.apicatalog.jsonld.JsonLd.expand(JsonDocument.of(json)).get(); + return com.apicatalog.jsonld.JsonLd.compact(JsonDocument.of(expanded), EMPTY_CONTEXT_DOCUMENT).get(); + } catch (JsonLdError e) { + return json; + } + } + /** * Get the ID value of an object diff --git a/utils/json-and-jsonld-utils/src/main/java/de/sovity/edc/utils/jsonld/vocab/Prop.java b/utils/json-and-jsonld-utils/src/main/java/de/sovity/edc/utils/jsonld/vocab/Prop.java index 06dc8c97d..5a68c7ee4 100644 --- a/utils/json-and-jsonld-utils/src/main/java/de/sovity/edc/utils/jsonld/vocab/Prop.java +++ b/utils/json-and-jsonld-utils/src/main/java/de/sovity/edc/utils/jsonld/vocab/Prop.java @@ -27,6 +27,7 @@ public class Prop { @UtilityClass public class Edc { public final String CTX = "https://w3id.org/edc/v0.0.1/ns/"; + public final String CTX_ALIAS = "edc"; public final String TYPE_ASSET = CTX + "Asset"; public final String TYPE_DATA_ADDRESS = CTX + "DataAddress"; public final String ID = CTX + "id"; @@ -41,6 +42,15 @@ public class Edc { public final String PROXY_PATH = CTX + "proxyPath"; public final String PROXY_QUERY_PARAMS = CTX + "proxyQueryParams"; public final String PROXY_BODY = CTX + "proxyBody"; + + // Transfer Request Related + public static String TYPE_TRANSFER_REQUEST = CTX + "TransferRequest"; + public final String CONNECTOR_ADDRESS = CTX + "connectorAddress"; + public final String CONTRACT_ID = CTX + "contractId"; + public final String CONNECTOR_ID = CTX + "connectorId"; + public final String ASSET_ID = CTX + "assetId"; + public final String DATA_DESTINATION = CTX + "dataDestination"; + public final String RECEIVER_HTTP_ENDPOINT = CTX + "receiverHttpEndpoint"; } /**