diff --git a/extensions/wrapper/client/build.gradle.kts b/extensions/wrapper/client/build.gradle.kts index 5f041fab8..fbd0ee8a3 100644 --- a/extensions/wrapper/client/build.gradle.kts +++ b/extensions/wrapper/client/build.gradle.kts @@ -43,12 +43,27 @@ dependencies { testAnnotationProcessor("org.projectlombok:lombok:${lombokVersion}") testImplementation("${edcGroup}:control-plane-core:${edcVersion}") - testImplementation("${edcGroup}:junit:${edcVersion}") + testImplementation("${edcGroup}:management-api:${edcVersion}") + testImplementation("${edcGroup}:api-observability:${edcVersion}") + testImplementation("${edcGroup}:configuration-filesystem:${edcVersion}") + testImplementation("${edcGroup}:control-plane-aggregate-services:${edcVersion}") testImplementation("${edcGroup}:http:${edcVersion}") - testImplementation("${edcGroup}:json-ld-spi:${edcVersion}") - testImplementation("${edcGroup}:dsp-http-spi:${edcVersion}") + testImplementation("${edcGroup}:dsp:${edcVersion}") + testImplementation("${edcGroup}:json-ld:${edcVersion}") + testImplementation("${edcGroup}:junit:${edcVersion}") + testImplementation("${edcGroup}:auth-tokenbased:${edcVersion}") + testImplementation("${edcGroup}:transfer-data-plane:${edcVersion}") + testImplementation("${edcGroup}:data-plane-selector-core:${edcVersion}") + testImplementation("${edcGroup}:data-plane-selector-client:${edcVersion}") + testImplementation("${edcGroup}:data-plane-http:${edcVersion}") + testImplementation("${edcGroup}:data-plane-framework:${edcVersion}") + testImplementation("${edcGroup}:data-plane-core:${edcVersion}") + testImplementation("${edcGroup}:data-plane-util:${edcVersion}") + testImplementation("${edcGroup}:iam-mock:${edcVersion}") testImplementation(project(":extensions:wrapper:wrapper")) testImplementation(project(":extensions:wrapper:wrapper-common-mappers")) + testImplementation(project(":extensions:test-backend-controller")) + testImplementation(project(":utils:test-connector-remote")) testImplementation("io.rest-assured:rest-assured:${restAssured}") testImplementation("org.junit.jupiter:junit-jupiter-api:5.10.0") testImplementation("${edcGroup}:data-plane-selector-core:${edcVersion}") diff --git a/extensions/wrapper/client/src/test/java/de/sovity/edc/client/ContractNegotiationApiServiceTest.java b/extensions/wrapper/client/src/test/java/de/sovity/edc/client/ContractNegotiationApiServiceTest.java new file mode 100644 index 000000000..3ad992851 --- /dev/null +++ b/extensions/wrapper/client/src/test/java/de/sovity/edc/client/ContractNegotiationApiServiceTest.java @@ -0,0 +1,216 @@ +/* + * Copyright (c) 2023 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.client; + +import de.sovity.edc.client.gen.model.ContractNegotiationRequest; +import de.sovity.edc.ext.wrapper.api.common.mappers.PolicyJsonMapper; +import de.sovity.edc.extension.e2e.connector.ConnectorRemote; +import de.sovity.edc.extension.e2e.connector.MockDataAddressRemote; +import jakarta.json.JsonObject; +import org.awaitility.Awaitility; +import org.eclipse.edc.connector.api.management.configuration.transform.ManagementApiTypeTransformerRegistry; +import org.eclipse.edc.core.transform.TypeTransformerRegistryImpl; +import org.eclipse.edc.junit.annotations.ApiTest; +import org.eclipse.edc.junit.extensions.EdcExtension; +import org.eclipse.edc.policy.model.Policy; +import org.eclipse.edc.policy.model.PolicyType; +import org.eclipse.edc.transform.spi.TypeTransformerRegistry; +import org.junit.jupiter.api.BeforeEach; +import org.junit.jupiter.api.Test; +import org.junit.jupiter.api.extension.ExtendWith; +import org.junit.jupiter.api.extension.RegisterExtension; + +import java.net.URI; +import java.time.Duration; +import java.util.UUID; + +import static de.sovity.edc.extension.e2e.connector.DataTransferTestUtil.validateDataTransferred; +import static de.sovity.edc.extension.e2e.connector.config.ConnectorConfigFactory.basicEdcConfig; +import static de.sovity.edc.extension.e2e.connector.config.ConnectorRemoteConfigFactory.fromConnectorConfig; +import static io.restassured.http.ContentType.JSON; +import static jakarta.json.Json.createObjectBuilder; +import static org.assertj.core.api.Assertions.assertThat; +import static org.assertj.core.api.InstanceOfAssertFactories.type; +import static org.eclipse.edc.connector.contract.spi.types.negotiation.ContractNegotiationStates.FINALIZED; +import static org.eclipse.edc.jsonld.spi.JsonLdKeywords.CONTEXT; +import static org.eclipse.edc.jsonld.spi.JsonLdKeywords.ID; +import static org.eclipse.edc.jsonld.spi.JsonLdKeywords.TYPE; +import static org.eclipse.edc.spi.CoreConstants.EDC_NAMESPACE; +import static org.eclipse.edc.spi.CoreConstants.EDC_PREFIX; + +@ApiTest +@ExtendWith(EdcExtension.class) +class ContractNegotiationApiServiceTest { + + + private static final String PROVIDER_PARTICIPANT_ID = "provider"; + private static final String CONSUMER_PARTICIPANT_ID = "consumer"; + private static final String TEST_BACKEND_TEST_DATA = UUID.randomUUID().toString(); + + @RegisterExtension + static EdcExtension providerEdcContext = new EdcExtension(); + @RegisterExtension + static EdcExtension consumerEdcContext = new EdcExtension(); + + private ConnectorRemote providerConnector; + private ConnectorRemote consumerConnector; + private MockDataAddressRemote dataAddress; + + private EdcClient consumerClient; + + @BeforeEach + void setup() { + var providerConfig = basicEdcConfig(PROVIDER_PARTICIPANT_ID, 24000); + providerEdcContext.setConfiguration(providerConfig.getProperties()); + providerConnector = new ConnectorRemote(fromConnectorConfig(providerConfig)); + + var consumerConfig = basicEdcConfig(CONSUMER_PARTICIPANT_ID, 25000); + consumerEdcContext.setConfiguration(consumerConfig.getProperties()); + consumerConnector = new ConnectorRemote(fromConnectorConfig(consumerConfig)); + + consumerClient = EdcClient.builder() + .managementApiUrl(consumerConnector.getConfig().getManagementEndpoint().getUri().toString()) + .managementApiKey(consumerConfig.getProperties().get("edc.api.auth.key")) + .build(); + + // We use the provider EDC as data sink / data source (it has the test-backend-controller extension) + dataAddress = new MockDataAddressRemote(providerConnector.getConfig().getDefaultEndpoint()); + } + + @Test + void testContractNegotiation__usingManagementApiOnly__todo_remove() { + // arrange + var assetId = UUID.randomUUID().toString(); + providerConnector.createDataOffer(assetId, dataAddress.getDataSourceUrl(TEST_BACKEND_TEST_DATA)); + String providerId = providerConnector.getParticipantId(); + URI providerProtocolApi = providerConnector.getConfig().getProtocolEndpoint().getUri(); + JsonObject destination = dataAddress.getDataSinkJsonLd(); + var dataset = consumerConnector.getDatasetForAsset(assetId, providerProtocolApi); + var contractId = consumerConnector.getDatasetContractId(dataset); + + var policyJsonLd = createObjectBuilder() + .add(CONTEXT, "https://www.w3.org/ns/odrl.jsonld") + .add(TYPE, "use") + .build() + .toString(); + + // act + var negotiationId = consumerConnector.prepareManagementApiCall() + .contentType(JSON) + .body(createObjectBuilder() + .add(CONTEXT, createObjectBuilder().add(EDC_PREFIX, EDC_NAMESPACE)) + .add(TYPE, "NegotiationInitiateRequestDto") + .add("connectorId", providerId) + .add("consumerId", consumerConnector.getParticipantId()) + .add("providerId", providerId) + .add("connectorAddress", providerProtocolApi.toString()) + .add("protocol", "dataspace-protocol-http") + .add("offer", createObjectBuilder() + .add("offerId", contractId.toString()) + .add("assetId", contractId.assetIdPart()) + .add("policy", policyJsonLd.toString()) + ) + .build()) + .when() + .post("/v2/contractnegotiations") + .then() + .statusCode(200) + .extract().body().jsonPath().getString(ID); + + Awaitility.await().atMost(Duration.ofSeconds(60)).untilAsserted(() -> { + var state = consumerConnector.prepareManagementApiCall() + .contentType(JSON) + .when() + .get("/v2/contractnegotiations/{id}/state", negotiationId) + .then() + .statusCode(200) + .extract().body().jsonPath().getString("'edc:state'"); + assertThat(state).isEqualTo(FINALIZED.name()); + }); + + var contractAgreementId = consumerConnector.getContractAgreementId(negotiationId); + + // assert + var transferProcessId = consumerConnector.initiateTransfer( + contractAgreementId, + assetId, + providerProtocolApi, + destination); + + assertThat(transferProcessId).isNotNull(); + validateDataTransferred(dataAddress.getDataSinkSpyUrl(), TEST_BACKEND_TEST_DATA); + } + + @Test + void testContractNegotiation() { + // arrange + TypeTransformerRegistry typeTransformerRegistry = providerEdcContext.getContext().getService(ManagementApiTypeTransformerRegistry.class, false); + Policy policy = Policy.Builder.newInstance() + .assigner("sampleAssigner") + .assignee("sampleAssignee") + .target("sampleTarget") + .inheritsFrom("sampleInheritsFrom") + .type(PolicyType.OFFER) + .extensibleProperty("sampleKey", "sampleValue") + .build(); + var policyJson = typeTransformerRegistry.transform(policy, JsonObject.class).getContent(); + var assetId = UUID.randomUUID().toString(); + providerConnector.createDataOffer(assetId, dataAddress.getDataSourceUrl(TEST_BACKEND_TEST_DATA)); + String providerId = providerConnector.getParticipantId(); + URI providerProtocolApi = providerConnector.getConfig().getProtocolEndpoint().getUri(); + JsonObject destination = dataAddress.getDataSinkJsonLd(); + var dataset = consumerConnector.getDatasetForAsset(assetId, providerProtocolApi); + var contractId = consumerConnector.getDatasetContractId(dataset); + + var contractNegotiationRequest = ContractNegotiationRequest.builder() + .protocol("dataspace-protocol-http") + .counterPartyAddress(providerProtocolApi.toString()) + .contractOfferId(contractId.toString()) + .assetId(contractId.assetIdPart()) + .policyJsonLd(String.valueOf(policyJson)) + .build(); + + // act + + var contractNegotiationDto = consumerClient.uiApi().initiateContractNegotiation(contractNegotiationRequest); + + Awaitility.await().atMost(Duration.ofSeconds(60)).untilAsserted(() -> { + var state = consumerConnector.prepareManagementApiCall() + .contentType(JSON) + .when() + .get("/v2/contractnegotiations/{id}/state", contractNegotiationDto.getContractNegotiationId()) + .then() + .statusCode(200) + .extract().body().jsonPath().getString("'edc:state'"); + assertThat(state).isEqualTo(FINALIZED.name()); + }); + + + var existingContractNegotiationDto = consumerClient.uiApi().getContractNegotiation(contractNegotiationDto.getContractNegotiationId()); + var state = existingContractNegotiationDto.getStatus(); + + + // assert + assertThat(state).isEqualTo(FINALIZED.name()); + var transferProcessId = consumerConnector.initiateTransfer( + contractNegotiationDto.getContractAgreementId(), + assetId, + providerProtocolApi, + destination); + + assertThat(transferProcessId).isNotNull(); + validateDataTransferred(dataAddress.getDataSinkSpyUrl(), TEST_BACKEND_TEST_DATA); + } +} diff --git a/extensions/wrapper/client/src/test/java/de/sovity/edc/client/PolicyDefinitionApiServiceTest.java b/extensions/wrapper/client/src/test/java/de/sovity/edc/client/PolicyDefinitionApiServiceTest.java index 06dee80a3..775f17ef2 100644 --- a/extensions/wrapper/client/src/test/java/de/sovity/edc/client/PolicyDefinitionApiServiceTest.java +++ b/extensions/wrapper/client/src/test/java/de/sovity/edc/client/PolicyDefinitionApiServiceTest.java @@ -15,12 +15,15 @@ package de.sovity.edc.client; +import de.sovity.edc.client.gen.model.ContractNegotiationRequest; import de.sovity.edc.client.gen.model.PolicyDefinitionCreateRequest; import de.sovity.edc.client.gen.model.PolicyDefinitionDto; import de.sovity.edc.client.gen.model.UiPolicyConstraint; import de.sovity.edc.client.gen.model.UiPolicyCreateRequest; import de.sovity.edc.client.gen.model.UiPolicyLiteral; +import jakarta.json.Json; import lombok.SneakyThrows; +import org.eclipse.edc.connector.spi.contractnegotiation.ContractNegotiationService; import org.eclipse.edc.connector.spi.policydefinition.PolicyDefinitionService; import org.eclipse.edc.junit.annotations.ApiTest; import org.eclipse.edc.junit.extensions.EdcExtension; @@ -32,7 +35,10 @@ import java.util.List; +import static jakarta.json.Json.createObjectBuilder; import static org.assertj.core.api.Assertions.assertThat; +import static org.eclipse.edc.jsonld.spi.JsonLdKeywords.CONTEXT; +import static org.eclipse.edc.jsonld.spi.JsonLdKeywords.TYPE; @ApiTest @ExtendWith(EdcExtension.class) diff --git a/extensions/wrapper/wrapper-common-mappers/build.gradle.kts b/extensions/wrapper/wrapper-common-mappers/build.gradle.kts index aeed32358..24777c0f5 100644 --- a/extensions/wrapper/wrapper-common-mappers/build.gradle.kts +++ b/extensions/wrapper/wrapper-common-mappers/build.gradle.kts @@ -15,6 +15,7 @@ dependencies { compileOnly("org.projectlombok:lombok:${lombokVersion}") api("${edcGroup}:policy-model:${edcVersion}") + api("${edcGroup}:transform-core:${edcVersion}") api(project(":extensions:wrapper:wrapper-common-api")) implementation("org.apache.commons:commons-lang3:3.13.0") diff --git a/extensions/wrapper/wrapper-common-mappers/src/main/java/de/sovity/edc/ext/wrapper/api/common/mappers/PolicyJsonMapper.java b/extensions/wrapper/wrapper-common-mappers/src/main/java/de/sovity/edc/ext/wrapper/api/common/mappers/PolicyJsonMapper.java new file mode 100644 index 000000000..631f2c494 --- /dev/null +++ b/extensions/wrapper/wrapper-common-mappers/src/main/java/de/sovity/edc/ext/wrapper/api/common/mappers/PolicyJsonMapper.java @@ -0,0 +1,31 @@ +package de.sovity.edc.ext.wrapper.api.common.mappers; + +import com.fasterxml.jackson.databind.ObjectMapper; +import de.sovity.edc.ext.wrapper.api.common.mappers.utils.AtomicConstraintMapper; +import de.sovity.edc.ext.wrapper.api.common.mappers.utils.ConstraintExtractor; +import de.sovity.edc.ext.wrapper.api.common.mappers.utils.MappingErrors; +import de.sovity.edc.ext.wrapper.api.common.mappers.utils.PolicyValidator; +import de.sovity.edc.ext.wrapper.api.common.model.UiPolicyCreateRequest; +import de.sovity.edc.ext.wrapper.api.common.model.UiPolicyDto; +import jakarta.json.JsonObject; +import lombok.RequiredArgsConstructor; +import lombok.SneakyThrows; +import org.eclipse.edc.policy.model.Action; +import org.eclipse.edc.policy.model.Constraint; +import org.eclipse.edc.policy.model.Permission; +import org.eclipse.edc.policy.model.Policy; +import org.eclipse.edc.policy.model.PolicyType; +import org.eclipse.edc.spi.result.Result; +import org.eclipse.edc.transform.spi.TypeTransformerRegistry; + +import java.util.ArrayList; + +@RequiredArgsConstructor +public class PolicyJsonMapper { + private final TypeTransformerRegistry transformerRegistry; + + @SneakyThrows + public Result getPolicyJsonLd(Policy policy) { + return transformerRegistry.transform(policy, JsonObject.class); + } +} diff --git a/extensions/wrapper/wrapper-common-mappers/src/main/java/de/sovity/edc/ext/wrapper/api/common/mappers/PolicyMapper.java b/extensions/wrapper/wrapper-common-mappers/src/main/java/de/sovity/edc/ext/wrapper/api/common/mappers/PolicyMapper.java index d1bc24085..6024958be 100644 --- a/extensions/wrapper/wrapper-common-mappers/src/main/java/de/sovity/edc/ext/wrapper/api/common/mappers/PolicyMapper.java +++ b/extensions/wrapper/wrapper-common-mappers/src/main/java/de/sovity/edc/ext/wrapper/api/common/mappers/PolicyMapper.java @@ -7,6 +7,7 @@ import de.sovity.edc.ext.wrapper.api.common.mappers.utils.PolicyValidator; import de.sovity.edc.ext.wrapper.api.common.model.UiPolicyCreateRequest; import de.sovity.edc.ext.wrapper.api.common.model.UiPolicyDto; +import jakarta.json.JsonObject; import lombok.RequiredArgsConstructor; import lombok.SneakyThrows; import org.eclipse.edc.policy.model.Action; @@ -14,6 +15,8 @@ import org.eclipse.edc.policy.model.Permission; import org.eclipse.edc.policy.model.Policy; import org.eclipse.edc.policy.model.PolicyType; +import org.eclipse.edc.spi.result.Result; +import org.eclipse.edc.transform.spi.TypeTransformerRegistry; import java.util.ArrayList; @@ -25,7 +28,7 @@ public class PolicyMapper { private final ObjectMapper jsonLdObjectMapper; private final ConstraintExtractor constraintExtractor; private final AtomicConstraintMapper atomicConstraintMapper; - + private final TypeTransformerRegistry transformerRegistry; /** * Builds a simplified UI Policy Model from an ODRL Policy. *

@@ -34,14 +37,13 @@ public class PolicyMapper { * @param policy ODRL policy * @return ui policy */ - @SneakyThrows public UiPolicyDto buildPolicyDto(Policy policy) { MappingErrors errors = MappingErrors.root(); var constraints = constraintExtractor.getPermissionConstraints(policy, errors); return UiPolicyDto.builder() - .policyJsonLd(jsonLdObjectMapper.writeValueAsString(policy)) + .policyJsonLd(getPolicyJsonLd(policy).toString()) .constraints(constraints) .errors(errors.getErrors()) .build(); @@ -71,4 +73,30 @@ public Policy buildPolicy(UiPolicyCreateRequest policyCreateDto) { .permission(permission) .build(); } + + /** + * Builds an ODRL Policy from JSON-LD. + *

+ * This operation is lossless. + * + * @param policyJsonLd policy + * @return ODRL policy + */ + @SneakyThrows + public Policy buildPolicy(String policyJsonLd) { + return jsonLdObjectMapper.readValue(policyJsonLd, Policy.class); + } + + /** + * Get an ODRL Policy as JSON-LD + *

+ * This operation is lossless. + * + * @param policy ODRL policy + * @return JSON-LD + */ + @SneakyThrows + public Result getPolicyJsonLd(Policy policy) { + return transformerRegistry.transform(policy,JsonObject.class); + } } diff --git a/extensions/wrapper/wrapper/src/main/java/de/sovity/edc/ext/wrapper/WrapperExtension.java b/extensions/wrapper/wrapper/src/main/java/de/sovity/edc/ext/wrapper/WrapperExtension.java index 464c389e4..f4e46c298 100644 --- a/extensions/wrapper/wrapper/src/main/java/de/sovity/edc/ext/wrapper/WrapperExtension.java +++ b/extensions/wrapper/wrapper/src/main/java/de/sovity/edc/ext/wrapper/WrapperExtension.java @@ -14,6 +14,9 @@ package de.sovity.edc.ext.wrapper; +import com.fasterxml.jackson.databind.SerializationFeature; +import com.fasterxml.jackson.datatype.jsr310.JavaTimeModule; +import lombok.SneakyThrows; import org.eclipse.edc.connector.api.management.configuration.ManagementApiConfiguration; import org.eclipse.edc.connector.contract.spi.negotiation.store.ContractNegotiationStore; import org.eclipse.edc.connector.contract.spi.offer.store.ContractDefinitionStore; @@ -31,8 +34,13 @@ import org.eclipse.edc.spi.system.ServiceExtension; import org.eclipse.edc.spi.system.ServiceExtensionContext; import org.eclipse.edc.spi.types.TypeManager; +import org.eclipse.edc.transform.spi.TypeTransformerRegistry; import org.eclipse.edc.web.spi.WebService; +import java.time.OffsetDateTime; + +import static org.eclipse.edc.spi.CoreConstants.JSON_LD; + public class WrapperExtension implements ServiceExtension { public static final String EXTENSION_NAME = "WrapperExtension"; @@ -60,6 +68,9 @@ public class WrapperExtension implements ServiceExtension { private TransferProcessService transferProcessService; @Inject private TransferProcessStore transferProcessStore; + + @Inject + private TypeTransformerRegistry transformerRegistry; @Inject private TypeManager typeManager; @Inject @@ -73,8 +84,12 @@ public String name() { } @Override + @SneakyThrows public void initialize(ServiceExtensionContext context) { - var objectMapper = typeManager.getMapper(); + var objectMapper = typeManager.getMapper(JSON_LD); + objectMapper.disable(SerializationFeature.WRITE_DATES_AS_TIMESTAMPS); + objectMapper.registerModule(new JavaTimeModule()); + var wrapperExtensionContext = WrapperExtensionContextBuilder.buildContext( context, @@ -90,7 +105,8 @@ public void initialize(ServiceExtensionContext context) { transferProcessStore, transferProcessService, contractDefinitionService, - policyDefinitionService + policyDefinitionService, + transformerRegistry ); wrapperExtensionContext.jaxRsResources().forEach(resource -> 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 1c995e013..0ee40c9aa 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 @@ -29,13 +29,16 @@ import de.sovity.edc.ext.wrapper.api.ui.pages.contracts.ContractAgreementPageApiService; import de.sovity.edc.ext.wrapper.api.ui.pages.contracts.ContractAgreementTransferApiService; import de.sovity.edc.ext.wrapper.api.ui.pages.contracts.ContractDefinitionApiService; +import de.sovity.edc.ext.wrapper.api.ui.pages.contracts.ContractNegotiationApiService; import de.sovity.edc.ext.wrapper.api.ui.pages.contracts.services.ContractAgreementDataFetcher; import de.sovity.edc.ext.wrapper.api.ui.pages.contracts.services.ContractAgreementPageCardBuilder; import de.sovity.edc.ext.wrapper.api.ui.pages.contracts.services.ContractDefinitionBuilder; +import de.sovity.edc.ext.wrapper.api.ui.pages.contracts.services.ContractNegotiationBuilder; import de.sovity.edc.ext.wrapper.api.ui.pages.contracts.services.TransferProcessStateService; import de.sovity.edc.ext.wrapper.api.ui.pages.contracts.services.TransferRequestBuilder; import de.sovity.edc.ext.wrapper.api.ui.pages.contracts.services.utils.ContractAgreementUtils; import de.sovity.edc.ext.wrapper.api.ui.pages.contracts.services.utils.ContractNegotiationUtils; +import de.sovity.edc.ext.wrapper.api.ui.pages.contracts.services.utils.ContractOfferMapper; import de.sovity.edc.ext.wrapper.api.ui.pages.contracts.services.utils.CriterionMapper; import de.sovity.edc.ext.wrapper.api.ui.pages.policy.PolicyDefinitionApiService; import de.sovity.edc.ext.wrapper.api.ui.pages.transferhistory.TransferHistoryPageApiService; @@ -60,6 +63,7 @@ import org.eclipse.edc.runtime.metamodel.annotation.Inject; import org.eclipse.edc.spi.asset.AssetIndex; import org.eclipse.edc.spi.system.ServiceExtensionContext; +import org.eclipse.edc.transform.spi.TypeTransformerRegistry; import java.util.List; @@ -89,8 +93,8 @@ public static WrapperExtensionContext buildContext( TransferProcessStore transferProcessStore, TransferProcessService transferProcessService, ContractDefinitionService contractDefinitionService, - PolicyDefinitionService policyDefinitionService - ) { + PolicyDefinitionService policyDefinitionService, + TypeTransformerRegistry transformerRegistry) { // UI API var operatorMapper = new OperatorMapper(); var criterionMapper = new CriterionMapper(operatorMapper); @@ -98,7 +102,7 @@ public static WrapperExtensionContext buildContext( var atomicConstraintMapper = new AtomicConstraintMapper(literalMapper, operatorMapper); var policyValidator = new PolicyValidator(); var constraintExtractor = new ConstraintExtractor(policyValidator, atomicConstraintMapper); - var policyMapper = new PolicyMapper(objectMapper, constraintExtractor, atomicConstraintMapper); + var policyMapper = new PolicyMapper(objectMapper, constraintExtractor, atomicConstraintMapper, transformerRegistry); var transferProcessStateService = new TransferProcessStateService(); var contractAgreementPageCardBuilder = new ContractAgreementPageCardBuilder( policyMapper, @@ -141,6 +145,9 @@ public static WrapperExtensionContext buildContext( transferProcessService ); var policyDefinitionApiService = new PolicyDefinitionApiService(policyDefinitionService, policyMapper); + var contractOfferMapper = new ContractOfferMapper(policyMapper); + var contractNegotiationBuilder = new ContractNegotiationBuilder(contractOfferMapper); + var contractNegotiationApiService = new ContractNegotiationApiService(contractNegotiationService, contractNegotiationBuilder); var uiResource = new UiResource( contractAgreementApiService, contractAgreementTransferApiService, @@ -148,7 +155,8 @@ public static WrapperExtensionContext buildContext( transferHistoryPageAssetFetcherService, assetApiService, policyDefinitionApiService, - contractDefinitionApiService + contractDefinitionApiService, + contractNegotiationApiService ); // Use Case API diff --git a/extensions/wrapper/wrapper/src/main/java/de/sovity/edc/ext/wrapper/api/ui/UiResource.java b/extensions/wrapper/wrapper/src/main/java/de/sovity/edc/ext/wrapper/api/ui/UiResource.java index 622010318..7fef753bd 100644 --- a/extensions/wrapper/wrapper/src/main/java/de/sovity/edc/ext/wrapper/api/ui/UiResource.java +++ b/extensions/wrapper/wrapper/src/main/java/de/sovity/edc/ext/wrapper/api/ui/UiResource.java @@ -22,12 +22,15 @@ import de.sovity.edc.ext.wrapper.api.ui.model.ContractAgreementTransferRequest; import de.sovity.edc.ext.wrapper.api.ui.model.ContractDefinitionRequest; import de.sovity.edc.ext.wrapper.api.ui.model.ContractDefinitionPage; +import de.sovity.edc.ext.wrapper.api.ui.model.ContractNegotiationDto; +import de.sovity.edc.ext.wrapper.api.ui.model.ContractNegotiationRequest; import de.sovity.edc.ext.wrapper.api.ui.model.IdResponseDto; 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.pages.asset.AssetApiService; import de.sovity.edc.ext.wrapper.api.ui.pages.contracts.ContractAgreementPageApiService; import de.sovity.edc.ext.wrapper.api.ui.pages.contracts.ContractAgreementTransferApiService; +import de.sovity.edc.ext.wrapper.api.ui.pages.contracts.ContractNegotiationApiService; import de.sovity.edc.ext.wrapper.api.ui.pages.policy.PolicyDefinitionApiService; import de.sovity.edc.ext.wrapper.api.ui.pages.contracts.ContractDefinitionApiService; import de.sovity.edc.ext.wrapper.api.ui.pages.transferhistory.TransferHistoryPageApiService; @@ -57,6 +60,7 @@ public class UiResource { private final AssetApiService assetApiService; private final PolicyDefinitionApiService policyDefinitionApiService; private final ContractDefinitionApiService contractDefinitionApiService; + private final ContractNegotiationApiService contractNegotiationApiService; @GET @Path("pages/contract-agreement-page") @@ -159,6 +163,7 @@ public PolicyDefinitionPage policyDefinitionPage() { public IdResponseDto createPolicyDefinition(PolicyDefinitionCreateRequest policyDefinitionDtoDto) { return policyDefinitionApiService.createPolicyDefinition(policyDefinitionDtoDto); } + @DELETE @Path("pages/policy-page/policy-definitions/{policyId}") @Produces(MediaType.APPLICATION_JSON) @@ -166,4 +171,22 @@ public IdResponseDto createPolicyDefinition(PolicyDefinitionCreateRequest policy public IdResponseDto deletePolicyDefinition(@PathParam("policyId") String policyId) { return policyDefinitionApiService.deletePolicyDefinition(policyId); } + + @POST + @Path("pages/catalog-page/contract-negotiations") + @Consumes(MediaType.APPLICATION_JSON) + @Produces(MediaType.APPLICATION_JSON) + @Operation(description = "Initiate a new Contract Negotiation") + public ContractNegotiationDto initiateContractNegotiation(ContractNegotiationRequest contractNegotiationRequest){ + return contractNegotiationApiService.initiateContractNegotiation(contractNegotiationRequest); + } + + @POST + @Path("pages/catalog-page/contract-negotiations/{contractNegotiationId}") + @Consumes(MediaType.APPLICATION_JSON) + @Produces(MediaType.APPLICATION_JSON) + @Operation(description = "Get Contract Negotiation Information") + public ContractNegotiationDto getContractNegotiation(@PathParam("contractNegotiationId") String contractNegotiationId){ + return contractNegotiationApiService.getContractNegotiation(contractNegotiationId); + } } diff --git a/extensions/wrapper/wrapper/src/main/java/de/sovity/edc/ext/wrapper/api/ui/model/ContractDefinitionRequest.java b/extensions/wrapper/wrapper/src/main/java/de/sovity/edc/ext/wrapper/api/ui/model/ContractDefinitionRequest.java index e726975c7..c0e0ab470 100644 --- a/extensions/wrapper/wrapper/src/main/java/de/sovity/edc/ext/wrapper/api/ui/model/ContractDefinitionRequest.java +++ b/extensions/wrapper/wrapper/src/main/java/de/sovity/edc/ext/wrapper/api/ui/model/ContractDefinitionRequest.java @@ -44,4 +44,5 @@ public class ContractDefinitionRequest { @Schema(description = "List of Criteria for the contract", requiredMode = Schema.RequiredMode.REQUIRED) private List assetSelector; + } diff --git a/extensions/wrapper/wrapper/src/main/java/de/sovity/edc/ext/wrapper/api/ui/model/ContractNegotiationDto.java b/extensions/wrapper/wrapper/src/main/java/de/sovity/edc/ext/wrapper/api/ui/model/ContractNegotiationDto.java new file mode 100644 index 000000000..9a0fac5d0 --- /dev/null +++ b/extensions/wrapper/wrapper/src/main/java/de/sovity/edc/ext/wrapper/api/ui/model/ContractNegotiationDto.java @@ -0,0 +1,47 @@ + +/* + * 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; +import lombok.AllArgsConstructor; +import lombok.Getter; +import lombok.RequiredArgsConstructor; +import lombok.Setter; +import lombok.ToString; + +import java.time.OffsetDateTime; + +@Getter +@Setter +@ToString +@AllArgsConstructor +@RequiredArgsConstructor +@Schema(description = "Contract Negotiation Information") +public class ContractNegotiationDto { + + @Schema(description = "Contract Negotiation Id", requiredMode = Schema.RequiredMode.REQUIRED) + private String contractNegotiationId; + + @Schema(description = "Contract Negotiation Creation Time", requiredMode = Schema.RequiredMode.REQUIRED) + private OffsetDateTime createdAt; + + @Schema(description = "Contract Agreement Id", requiredMode = Schema.RequiredMode.REQUIRED) + private String contractAgreementId; + + @Schema(description = "Status of the Contract Negotiation ", requiredMode = Schema.RequiredMode.REQUIRED) + private String status; + +} diff --git a/extensions/wrapper/wrapper/src/main/java/de/sovity/edc/ext/wrapper/api/ui/model/ContractNegotiationRequest.java b/extensions/wrapper/wrapper/src/main/java/de/sovity/edc/ext/wrapper/api/ui/model/ContractNegotiationRequest.java new file mode 100644 index 000000000..31e961192 --- /dev/null +++ b/extensions/wrapper/wrapper/src/main/java/de/sovity/edc/ext/wrapper/api/ui/model/ContractNegotiationRequest.java @@ -0,0 +1,47 @@ + +/* + * 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; +import lombok.AllArgsConstructor; +import lombok.Getter; +import lombok.RequiredArgsConstructor; +import lombok.Setter; +import lombok.ToString; + +@Getter +@Setter +@ToString +@AllArgsConstructor +@RequiredArgsConstructor +@Schema(description = "Data for initiating a Contract Negotiation") +public class ContractNegotiationRequest { + + @Schema(description = "Protocol", requiredMode = Schema.RequiredMode.REQUIRED) + private String protocol; + + @Schema(description = "Counter Party Address", requiredMode = Schema.RequiredMode.REQUIRED) + private String counterPartyAddress; + + @Schema(description = "Contract Offer Dto ", requiredMode = Schema.RequiredMode.REQUIRED) + private String contractOfferId; + + @Schema(description = "Policy JsonLd", requiredMode = Schema.RequiredMode.REQUIRED) + private String policyJsonLd; + + @Schema(description = "Asset ID", requiredMode = Schema.RequiredMode.REQUIRED) + private String assetId; +} diff --git a/extensions/wrapper/wrapper/src/main/java/de/sovity/edc/ext/wrapper/api/ui/pages/contracts/ContractNegotiationApiService.java b/extensions/wrapper/wrapper/src/main/java/de/sovity/edc/ext/wrapper/api/ui/pages/contracts/ContractNegotiationApiService.java new file mode 100644 index 000000000..2ee5de708 --- /dev/null +++ b/extensions/wrapper/wrapper/src/main/java/de/sovity/edc/ext/wrapper/api/ui/pages/contracts/ContractNegotiationApiService.java @@ -0,0 +1,46 @@ +/* + * 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.pages.contracts; + +import de.sovity.edc.ext.wrapper.api.ui.model.ContractNegotiationDto; +import de.sovity.edc.ext.wrapper.api.ui.model.ContractNegotiationRequest; +import de.sovity.edc.ext.wrapper.api.ui.pages.contracts.services.ContractNegotiationBuilder; +import lombok.RequiredArgsConstructor; +import org.eclipse.edc.connector.spi.contractnegotiation.ContractNegotiationService; +import org.jetbrains.annotations.NotNull; + +import static de.sovity.edc.ext.wrapper.utils.EdcDateUtils.utcMillisToOffsetDateTime; + + +@RequiredArgsConstructor +public class ContractNegotiationApiService { + private final ContractNegotiationService contractNegotiationService; + private final ContractNegotiationBuilder contractNegotiationBuilder; + + @NotNull + public ContractNegotiationDto initiateContractNegotiation(ContractNegotiationRequest request) { + var contractRequest = contractNegotiationBuilder.buildContractNegotiation(request); + var contractNegotiation = contractNegotiationService.initiateNegotiation(contractRequest); + var contractNegotiationStatus = contractNegotiationService.getState(contractNegotiation.getId()); + return new ContractNegotiationDto(contractNegotiation.getId(), utcMillisToOffsetDateTime(contractNegotiation.getCreatedAt()) , contractNegotiation.getContractAgreement().getId(), contractNegotiationStatus); + } + + @NotNull + public ContractNegotiationDto getContractNegotiation(String contractNegotiationId) { + var contractNegotiation = contractNegotiationService.findbyId(contractNegotiationId); + var contractNegotiationStatus = contractNegotiationService.getState(contractNegotiation.getId()); + return new ContractNegotiationDto(contractNegotiation.getId(), utcMillisToOffsetDateTime(contractNegotiation.getCreatedAt()) , contractNegotiation.getContractAgreement().getId(), contractNegotiationStatus); + } +} diff --git a/extensions/wrapper/wrapper/src/main/java/de/sovity/edc/ext/wrapper/api/ui/pages/contracts/services/ContractNegotiationBuilder.java b/extensions/wrapper/wrapper/src/main/java/de/sovity/edc/ext/wrapper/api/ui/pages/contracts/services/ContractNegotiationBuilder.java new file mode 100644 index 000000000..e24da83de --- /dev/null +++ b/extensions/wrapper/wrapper/src/main/java/de/sovity/edc/ext/wrapper/api/ui/pages/contracts/services/ContractNegotiationBuilder.java @@ -0,0 +1,48 @@ + + +/* + * Copyright (c) 2023 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.pages.contracts.services; + + +import com.fasterxml.jackson.core.JsonProcessingException; +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.pages.contracts.services.utils.CriterionMapper; +import lombok.RequiredArgsConstructor; +import org.eclipse.edc.connector.contract.spi.types.negotiation.ContractNegotiation; +import org.eclipse.edc.connector.contract.spi.types.negotiation.ContractRequest; +import org.eclipse.edc.connector.contract.spi.types.offer.ContractDefinition; + +import de.sovity.edc.ext.wrapper.api.ui.pages.contracts.services.utils.ContractOfferMapper; + + +@RequiredArgsConstructor +public class ContractNegotiationBuilder { + + private final ContractOfferMapper contractOfferMapper; + + public ContractRequest buildContractNegotiation(ContractNegotiationRequest request) { + var protocol = request.getProtocol(); + var counterPartyAddress = request.getCounterPartyAddress(); + + + return ContractRequest.Builder.newInstance() + .counterPartyAddress(counterPartyAddress) + .protocol(protocol) + .contractOffer(contractOfferMapper.buildContractOffer(request)) + .build(); + } +} diff --git a/extensions/wrapper/wrapper/src/main/java/de/sovity/edc/ext/wrapper/api/ui/pages/contracts/services/utils/ContractOfferMapper.java b/extensions/wrapper/wrapper/src/main/java/de/sovity/edc/ext/wrapper/api/ui/pages/contracts/services/utils/ContractOfferMapper.java new file mode 100644 index 000000000..ccf4ab58e --- /dev/null +++ b/extensions/wrapper/wrapper/src/main/java/de/sovity/edc/ext/wrapper/api/ui/pages/contracts/services/utils/ContractOfferMapper.java @@ -0,0 +1,27 @@ +package de.sovity.edc.ext.wrapper.api.ui.pages.contracts.services.utils; + + +import de.sovity.edc.ext.wrapper.api.common.mappers.PolicyMapper; +import de.sovity.edc.ext.wrapper.api.ui.model.ContractNegotiationRequest; +import lombok.RequiredArgsConstructor; +import org.eclipse.edc.connector.contract.spi.types.offer.ContractOffer; + +; + + +@RequiredArgsConstructor +public class ContractOfferMapper { + + /** + * This Object Mapper must be able to handle JSON-LD serialization / deserialization. + */ + private final PolicyMapper policyMapper; + + public ContractOffer buildContractOffer(ContractNegotiationRequest contractRequest) { + return ContractOffer.Builder.newInstance() + .id(contractRequest.getContractOfferId()) + .policy(policyMapper.buildPolicy(contractRequest.getPolicyJsonLd())) + .assetId(contractRequest.getAssetId()) + .build(); + } +} diff --git a/utils/test-connector-remote/build.gradle.kts b/utils/test-connector-remote/build.gradle.kts index 1a7b62aab..ccb23e1d6 100644 --- a/utils/test-connector-remote/build.gradle.kts +++ b/utils/test-connector-remote/build.gradle.kts @@ -18,6 +18,7 @@ dependencies { implementation("org.apache.commons:commons-lang3:3.13.0") api("${edcGroup}:junit:${edcVersion}") + api("org.awaitility:awaitility:${awaitilityVersion}") implementation("${edcGroup}:sql-core:${edcVersion}") implementation("${edcGroup}:json-ld-spi:${edcVersion}") implementation("${edcGroup}:json-ld:${edcVersion}") @@ -26,5 +27,4 @@ dependencies { implementation("org.testcontainers:junit-jupiter:${testcontainersVersion}") implementation("org.testcontainers:postgresql:${testcontainersVersion}") implementation("io.rest-assured:rest-assured:${restAssured}") - implementation("org.awaitility:awaitility:${awaitilityVersion}") } diff --git a/utils/test-connector-remote/src/main/java/de/sovity/edc/extension/e2e/connector/ConnectorRemote.java b/utils/test-connector-remote/src/main/java/de/sovity/edc/extension/e2e/connector/ConnectorRemote.java index 1d7befd66..e94e6acac 100644 --- a/utils/test-connector-remote/src/main/java/de/sovity/edc/extension/e2e/connector/ConnectorRemote.java +++ b/utils/test-connector-remote/src/main/java/de/sovity/edc/extension/e2e/connector/ConnectorRemote.java @@ -250,7 +250,7 @@ private String getContractNegotiationField(String negotiationId) { .then() .statusCode(200) .extract().body().jsonPath() - .getString("'edc:contractAgreementId'"); + .getString("'edc:contractOfferId'"); } public String getContractNegotiationState(String id) { @@ -357,7 +357,7 @@ public void createDataOffer( noConstraintPolicyId); } - private RequestSpecification prepareManagementApiCall() { + public RequestSpecification prepareManagementApiCall() { var managementConfig = config.getManagementEndpoint(); var managementBaseUri = managementConfig.getUri().toString(); if (managementConfig.authProvider() instanceof NoneAuthProvider) { @@ -379,7 +379,7 @@ private Header getAuthHeader() { } - private ContractId getDatasetContractId(JsonObject dataset) { + public ContractId getDatasetContractId(JsonObject dataset) { var id = dataset.getJsonArray(ODRL_POLICY_ATTRIBUTE).get(0).asJsonObject().getString(ID); return ContractId.parseId(id).getContent(); } diff --git a/utils/test-connector-remote/src/main/java/de/sovity/edc/extension/e2e/connector/ContractNegotiationTestUtil.java b/utils/test-connector-remote/src/main/java/de/sovity/edc/extension/e2e/connector/ContractNegotiationTestUtil.java new file mode 100644 index 000000000..9e8b0856c --- /dev/null +++ b/utils/test-connector-remote/src/main/java/de/sovity/edc/extension/e2e/connector/ContractNegotiationTestUtil.java @@ -0,0 +1,59 @@ +/* + * Copyright (c) 2023 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 - init + */ + +package de.sovity.edc.extension.e2e.connector; + +import jakarta.json.JsonObject; +import lombok.AccessLevel; +import lombok.NoArgsConstructor; + +import java.time.Duration; + +import static io.restassured.RestAssured.when; +import static jakarta.json.Json.createObjectBuilder; +import static org.assertj.core.api.Assertions.assertThat; +import static org.awaitility.Awaitility.await; +import static org.eclipse.edc.jsonld.spi.JsonLdKeywords.TYPE; +import static org.eclipse.edc.spi.CoreConstants.EDC_NAMESPACE; + +@SuppressWarnings("java:S5960") +@NoArgsConstructor(access = AccessLevel.PRIVATE) +public class ContractNegotiationTestUtil { + + public static final Duration TIMEOUT = Duration.ofSeconds(20); + + /*public static JsonObject buildDataAddressJsonLd(String baseUrl, String method) { + return createObjectBuilder() + .add(TYPE, EDC_NAMESPACE + "DataAddress") + .add(EDC_NAMESPACE + "type", "HttpData") + .add(EDC_NAMESPACE + "properties", createObjectBuilder() + .add(EDC_NAMESPACE + "baseUrl", baseUrl) + .add(EDC_NAMESPACE + "method", method) + .build()) + .build(); + }*/ + + public static void validateContractNegotiation() { + + } + public static void validateDataTransferred(String checkUrl, String expectedData) { + await().atMost(TIMEOUT).untilAsserted(() -> { + var actual = when() + .get(checkUrl) + .then() + .statusCode(200) + .extract().body().asString(); + assertThat(actual).isEqualTo(expectedData); + }); + } +} diff --git a/utils/test-connector-remote/src/main/java/de/sovity/edc/extension/e2e/connector/DataTransferTestUtil.java b/utils/test-connector-remote/src/main/java/de/sovity/edc/extension/e2e/connector/DataTransferTestUtil.java index 7a0088c42..b84058f08 100644 --- a/utils/test-connector-remote/src/main/java/de/sovity/edc/extension/e2e/connector/DataTransferTestUtil.java +++ b/utils/test-connector-remote/src/main/java/de/sovity/edc/extension/e2e/connector/DataTransferTestUtil.java @@ -16,6 +16,7 @@ import jakarta.json.JsonObject; import lombok.AccessLevel; import lombok.NoArgsConstructor; +import org.eclipse.edc.policy.model.Policy; import java.time.Duration; @@ -43,6 +44,7 @@ public static JsonObject buildDataAddressJsonLd(String baseUrl, String method) { .build(); } + public static void validateDataTransferred(String checkUrl, String expectedData) { await().atMost(TIMEOUT).untilAsserted(() -> { var actual = when()