Skip to content

Commit

Permalink
feat: handle custom transfer process properties in the backend (#565)
Browse files Browse the repository at this point in the history
  • Loading branch information
richardtreier authored Oct 13, 2023
1 parent 86f86d0 commit 9754e5d
Show file tree
Hide file tree
Showing 12 changed files with 248 additions and 97 deletions.
Original file line number Diff line number Diff line change
Expand Up @@ -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;
Expand Down Expand Up @@ -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")
Expand Down

This file was deleted.

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

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

0 comments on commit 9754e5d

Please sign in to comment.