diff --git a/CHANGELOG.md b/CHANGELOG.md index 78e67582d..feb845920 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -17,6 +17,7 @@ All notable changes to this project will be documented in this file. #### Patch Changes - Improved `:extensions:wrapper:wrapper-common-mappers` for broker: `AssetJsonLdUtils`, made some methods public. +- Added example for using the API Wrapper to offer and consume data. ### Deployment Migration Notes diff --git a/docs/getting-started/documentation/api_wrapper.md b/docs/getting-started/documentation/api_wrapper.md index 002c389d1..e11c3ee4d 100644 --- a/docs/getting-started/documentation/api_wrapper.md +++ b/docs/getting-started/documentation/api_wrapper.md @@ -3,10 +3,17 @@ Manging a sovity EDC Connector via the API Wrapper Java Client Library Introduction to the sovity EDC API Wrapper ======== -The sovity EDC API Wrapper contains several APIs, of which some are available in either our sovity EDC CE or our sovity CE EE / Connector-as-a-Servcie (CaaS). These APIs are made accessible via type-safe generated client libraries. Please note that most of these APIs are not yet complete and are under development: -- **Use Case API**: Generic API for Use Case Applications. Its goal is to replace the Management API, so there can be stable endpoints across milestones in the auto-generated client libraries. It's still in development, so expect many new endpoints to be added here in the near future. -- **UI API**: API endpoints for the sovity EDC UI: These endpoints might contain interesting data, that a Use Case Application might profit from, but expect these endpoints to be unstable and subject to change. -- **Enterprise Edition API**: Special API endpoint only available in the Connector-as-a-Service (CaaS). Features such as File Storage are currently in development, but to be expected in the near future. +The sovity EDC API Wrapper contains several APIs, of which some are available in either our sovity EDC CE or our sovity +CE EE / Connector-as-a-Servcie (CaaS). These APIs are made accessible via type-safe generated client libraries. Please +note that most of these APIs are not yet complete and are under development: + +- **Use Case API**: Generic API for Use Case Applications. Its goal is to replace the Management API, so there can be + stable endpoints across milestones in the auto-generated client libraries. It's still in development, so expect many + new endpoints to be added here in the near future. +- **UI API**: API endpoints for the sovity EDC UI: These endpoints might contain interesting data, that a Use Case + Application might profit from, but expect these endpoints to be unstable and subject to change. +- **Enterprise Edition API**: Special API endpoint only available in the Connector-as-a-Service (CaaS). Features such as + File Storage are currently in development, but to be expected in the near future. Using the Java Client Library ======== @@ -15,20 +22,24 @@ This requires JDK11 or higher, and either a Gradle or Maven project. Installing The Java Client Library ======== Connect your Maven or Gradle Project to the Github Maven Registry -- Maven: https://docs.github.com/en/packages/working-with-a-github-packages-registry/working-with-the-apache-maven-registry#authenticating-to-github-packages -- Gradle: https://docs.github.com/en/packages/working-with-a-github-packages-registry/working-with-the-gradle-registry#authenticating-to-github-packages + +- +Maven: https://docs.github.com/en/packages/working-with-a-github-packages-registry/working-with-the-apache-maven-registry#authenticating-to-github-packages +- +Gradle: https://docs.github.com/en/packages/working-with-a-github-packages-registry/working-with-the-gradle-registry#authenticating-to-github-packages - This might require a Github Personal Access Token (PAT) -Add the Java Client Library to your Maven/Gradle project: https://github.com/sovity/edc-extensions/packages/1825774 + Add the Java Client Library to your Maven/Gradle project: https://github.com/sovity/edc-extensions/packages/1825774 Configuring The Client ======== -- Configure the Client with either an API Key or OAuth2 Client Credentials: https://github.com/sovity/edc-extensions/tree/main/extensions/wrapper/clients/java-client#usage + +- Configure the Client with either an API Key or OAuth2 Client + Credentials: https://github.com/sovity/edc-extensions/tree/main/extensions/wrapper/clients/java-client#usage - Your management API URL should look like https://your-connector-name.prod-sovity.azure.sovity.io/control/data Using The Client ======== Feel free to use the endpoints of the aforementioned API groups. -Example Usage of a Use Case API Endpoint: -```java -KpiResult kpiResult = client.useCaseApi().getKpis(); -``` + +A full example providing and consuming a data offer using the API Wrapper Client Library can be found +in [ApiWrapperDemoTest.java](../../../launchers/connectors/sovity-dev/src/test/java/de/sovity/edc/e2e/ApiWrapperDemoTest.java). diff --git a/extensions/wrapper/clients/java-client-example/README.md b/extensions/wrapper/clients/java-client-example/README.md index 0882e9c03..3e2212acc 100644 --- a/extensions/wrapper/clients/java-client-example/README.md +++ b/extensions/wrapper/clients/java-client-example/README.md @@ -18,6 +18,9 @@ Example Quarkus Application that uses the Java API Client Library. +A full example providing and consuming a data offer using the API Wrapper Client Library can be found +in [ApiWrapperDemoTest.java](../../../../launchers/connectors/sovity-dev/src/test/java/de/sovity/edc/e2e/ApiWrapperDemoTest.java). + ## License Apache License 2.0 - see [LICENSE](../../../../LICENSE) diff --git a/extensions/wrapper/clients/java-client/README.md b/extensions/wrapper/clients/java-client/README.md index 3d9bd9073..d530806f6 100644 --- a/extensions/wrapper/clients/java-client/README.md +++ b/extensions/wrapper/clients/java-client/README.md @@ -33,6 +33,11 @@ An example project using this client can be found [here](../java-client-example) ## Usage +### Example Consuming and Providing a Data Offer + +A full example providing and consuming a data offer using the API Wrapper Client Library can be found +in [ApiWrapperDemoTest.java](../../../../launchers/connectors/sovity-dev/src/test/java/de/sovity/edc/e2e/ApiWrapperDemoTest.java). + ### Example Using API Key Auth ```java diff --git a/launchers/connectors/sovity-dev/src/test/java/de/sovity/edc/e2e/ApiWrapperDemoTest.java b/launchers/connectors/sovity-dev/src/test/java/de/sovity/edc/e2e/ApiWrapperDemoTest.java new file mode 100644 index 000000000..d57e6efcb --- /dev/null +++ b/launchers/connectors/sovity-dev/src/test/java/de/sovity/edc/e2e/ApiWrapperDemoTest.java @@ -0,0 +1,223 @@ +/* + * 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.e2e; + +import de.sovity.edc.client.EdcClient; +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.InitiateTransferRequest; +import de.sovity.edc.client.gen.model.OperatorDto; +import de.sovity.edc.client.gen.model.PolicyDefinitionCreateRequest; +import de.sovity.edc.client.gen.model.UiAssetCreateRequest; +import de.sovity.edc.client.gen.model.UiContractNegotiation; +import de.sovity.edc.client.gen.model.UiContractOffer; +import de.sovity.edc.client.gen.model.UiCriterion; +import de.sovity.edc.client.gen.model.UiCriterionLiteral; +import de.sovity.edc.client.gen.model.UiCriterionLiteralType; +import de.sovity.edc.client.gen.model.UiCriterionOperator; +import de.sovity.edc.client.gen.model.UiDataOffer; +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 de.sovity.edc.client.gen.model.UiPolicyLiteralType; +import de.sovity.edc.extension.e2e.connector.ConnectorRemote; +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.jsonld.vocab.Prop; +import org.awaitility.Awaitility; +import org.eclipse.edc.junit.extensions.EdcExtension; +import org.junit.jupiter.api.BeforeEach; +import org.junit.jupiter.api.Test; +import org.junit.jupiter.api.extension.RegisterExtension; + +import java.time.OffsetDateTime; +import java.util.List; +import java.util.Map; + +import static de.sovity.edc.extension.e2e.connector.DataTransferTestUtil.validateDataTransferred; +import static de.sovity.edc.extension.e2e.connector.config.ConnectorConfigFactory.forTestDatabase; +import static de.sovity.edc.extension.e2e.connector.config.ConnectorRemoteConfigFactory.fromConnectorConfig; +import static org.assertj.core.api.Assertions.assertThat; + +class ApiWrapperDemoTest { + + private static final String PROVIDER_PARTICIPANT_ID = "provider"; + private static final String CONSUMER_PARTICIPANT_ID = "consumer"; + + @RegisterExtension + static EdcExtension providerEdcContext = new EdcExtension(); + @RegisterExtension + static EdcExtension consumerEdcContext = new EdcExtension(); + + @RegisterExtension + static final TestDatabase PROVIDER_DATABASE = TestDatabaseFactory.getTestDatabase(1); + @RegisterExtension + static final TestDatabase CONSUMER_DATABASE = TestDatabaseFactory.getTestDatabase(2); + + private ConnectorRemote providerConnector; + private ConnectorRemote consumerConnector; + + private EdcClient providerClient; + private EdcClient consumerClient; + private MockDataAddressRemote dataAddress; + private final String dataOfferData = "expected data 123"; + + private final String dataOfferId = "my-data-offer-2023-11"; + + @BeforeEach + void setup() { + // set up provider EDC + Client + var providerConfig = forTestDatabase(PROVIDER_PARTICIPANT_ID, 21000, PROVIDER_DATABASE); + providerEdcContext.setConfiguration(providerConfig.getProperties()); + providerConnector = new ConnectorRemote(fromConnectorConfig(providerConfig)); + + providerClient = EdcClient.builder() + .managementApiUrl(providerConfig.getManagementEndpoint().getUri().toString()) + .managementApiKey(providerConfig.getProperties().get("edc.api.auth.key")) + .build(); + + // set up consumer EDC + Client + var consumerConfig = forTestDatabase(CONSUMER_PARTICIPANT_ID, 23000, CONSUMER_DATABASE); + consumerEdcContext.setConfiguration(consumerConfig.getProperties()); + consumerConnector = new ConnectorRemote(fromConnectorConfig(consumerConfig)); + + consumerClient = EdcClient.builder() + .managementApiUrl(consumerConfig.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 provide_and_consume() { + // provider: create data offer + createPolicy(); + createAsset(); + createContractDefinition(); + + // consumer: negotiate contract and transfer data + var dataOffers = consumerClient.uiApi().getCatalogPageDataOffers(getProtocolEndpoint(providerConnector)); + var negotiation = initiateNegotiation(dataOffers.get(0), dataOffers.get(0).getContractOffers().get(0)); + negotiation = awaitNegotiationDone(negotiation.getContractNegotiationId()); + initiateTransfer(negotiation); + + // check data sink + validateDataTransferred(dataAddress.getDataSinkSpyUrl(), dataOfferData); + } + + private void createAsset() { + var asset = UiAssetCreateRequest.builder() + .id(dataOfferId) + .title("My Data Offer") + .description("Example Data Offer.") + .version("2023-11") + .language("EN") + .publisherHomepage("https://my-department.my-org.com/my-data-offer") + .licenseUrl("https://my-department.my-org.com/my-data-offer#license") + .dataAddressProperties(Map.of( + Prop.Edc.TYPE, "HttpData", + Prop.Edc.METHOD, "GET", + Prop.Edc.BASE_URL, dataAddress.getDataSourceUrl(dataOfferData) + )) + .build(); + + providerClient.uiApi().createAsset(asset); + } + + private void createPolicy() { + var afterYesterday = UiPolicyConstraint.builder() + .left("POLICY_EVALUATION_TIME") + .operator(OperatorDto.GT) + .right(UiPolicyLiteral.builder() + .type(UiPolicyLiteralType.STRING) + .value(OffsetDateTime.now().minusDays(1).toString()) + .build()) + .build(); + + var beforeTomorrow = UiPolicyConstraint.builder() + .left("POLICY_EVALUATION_TIME") + .operator(OperatorDto.LT) + .right(UiPolicyLiteral.builder() + .type(UiPolicyLiteralType.STRING) + .value(OffsetDateTime.now().plusDays(1).toString()) + .build()) + .build(); + + var policyDefinition = PolicyDefinitionCreateRequest.builder() + .policyDefinitionId(dataOfferId) + .policy(UiPolicyCreateRequest.builder() + .constraints(List.of(afterYesterday, beforeTomorrow)) + .build()) + .build(); + + providerClient.uiApi().createPolicyDefinition(policyDefinition); + } + + private void createContractDefinition() { + var contractDefinition = ContractDefinitionRequest.builder() + .contractDefinitionId(dataOfferId) + .accessPolicyId(dataOfferId) + .contractPolicyId(dataOfferId) + .assetSelector(List.of(UiCriterion.builder() + .operandLeft(Prop.Edc.ID) + .operator(UiCriterionOperator.EQ) + .operandRight(UiCriterionLiteral.builder() + .type(UiCriterionLiteralType.VALUE) + .value(dataOfferId) + .build()) + .build())) + .build(); + + providerClient.uiApi().createContractDefinition(contractDefinition); + } + + private UiContractNegotiation initiateNegotiation(UiDataOffer dataOffer, UiContractOffer contractOffer) { + var negotiationRequest = ContractNegotiationRequest.builder() + .counterPartyAddress(dataOffer.getEndpoint()) + .counterPartyParticipantId(dataOffer.getParticipantId()) + .assetId(dataOffer.getAsset().getAssetId()) + .contractOfferId(contractOffer.getContractOfferId()) + .policyJsonLd(contractOffer.getPolicy().getPolicyJsonLd()) + .build(); + + return consumerClient.uiApi().initiateContractNegotiation(negotiationRequest); + } + + private UiContractNegotiation awaitNegotiationDone(String negotiationId) { + var negotiation = Awaitility.await().atMost(consumerConnector.timeout).until( + () -> consumerClient.uiApi().getContractNegotiation(negotiationId), + it -> it.getState().getSimplifiedState() != ContractNegotiationSimplifiedState.IN_PROGRESS + ); + + assertThat(negotiation.getState().getSimplifiedState()).isEqualTo(ContractNegotiationSimplifiedState.AGREED); + return negotiation; + } + + private void initiateTransfer(UiContractNegotiation negotiation) { + var contractAgreementId = negotiation.getContractAgreementId(); + var transferRequest = InitiateTransferRequest.builder() + .contractAgreementId(contractAgreementId) + .dataSinkProperties(dataAddress.getDataSinkProperties()) + .build(); + consumerClient.uiApi().initiateTransfer(transferRequest); + } + + private String getProtocolEndpoint(ConnectorRemote connector) { + return connector.getConfig().getProtocolEndpoint().getUri().toString(); + } +}