From e4523e3cbe3d20675bacbf882bade8cfe9b6f1bf Mon Sep 17 00:00:00 2001 From: "ameerali.khan" <ameerali.khan@fit.fraunhofer.de> Date: Tue, 13 Jun 2023 15:32:19 +0200 Subject: [PATCH] Add Edc Sample for testing --- .idea/.gitignore | 8 + .idea/gradle.xml | 27 ++ .idea/misc.xml | 10 + .idea/uiDesigner.xml | 124 +++++++ .idea/vcs.xml | 6 + sample/transfer/README.md | 62 ++++ .../transfer-01-file-transfer/README.md | 307 ++++++++++++++++++ .../contractoffer.json | 24 ++ .../file-transfer-consumer/build.gradle.kts | 49 +++ .../file-transfer-consumer/config.properties | 13 + .../file-transfer-provider/build.gradle.kts | 44 +++ .../file-transfer-provider/config.properties | 14 + .../filetransfer.json | 20 ++ .../status-checker/build.gradle.kts | 21 ++ .../checker/SampleFileStatusChecker.java | 39 +++ .../checker/SampleStatusCheckerExtension.java | 41 +++ ...rg.eclipse.edc.spi.system.ServiceExtension | 15 + .../transfer-file-local/build.gradle.kts | 33 ++ .../extension/api/FileTransferDataSink.java | 82 +++++ .../api/FileTransferDataSinkFactory.java | 65 ++++ .../extension/api/FileTransferDataSource.java | 54 +++ .../api/FileTransferDataSourceFactory.java | 58 ++++ .../extension/api/FileTransferExtension.java | 122 +++++++ ...rg.eclipse.edc.spi.system.ServiceExtension | 1 + 24 files changed, 1239 insertions(+) create mode 100644 .idea/.gitignore create mode 100644 .idea/gradle.xml create mode 100644 .idea/misc.xml create mode 100644 .idea/uiDesigner.xml create mode 100644 .idea/vcs.xml create mode 100644 sample/transfer/README.md create mode 100644 sample/transfer/transfer-01-file-transfer/README.md create mode 100644 sample/transfer/transfer-01-file-transfer/contractoffer.json create mode 100644 sample/transfer/transfer-01-file-transfer/file-transfer-consumer/build.gradle.kts create mode 100644 sample/transfer/transfer-01-file-transfer/file-transfer-consumer/config.properties create mode 100644 sample/transfer/transfer-01-file-transfer/file-transfer-provider/build.gradle.kts create mode 100644 sample/transfer/transfer-01-file-transfer/file-transfer-provider/config.properties create mode 100644 sample/transfer/transfer-01-file-transfer/filetransfer.json create mode 100644 sample/transfer/transfer-01-file-transfer/status-checker/build.gradle.kts create mode 100644 sample/transfer/transfer-01-file-transfer/status-checker/src/main/java/org/eclipse/edc/sample/extension/checker/SampleFileStatusChecker.java create mode 100644 sample/transfer/transfer-01-file-transfer/status-checker/src/main/java/org/eclipse/edc/sample/extension/checker/SampleStatusCheckerExtension.java create mode 100644 sample/transfer/transfer-01-file-transfer/status-checker/src/main/resources/META-INF/services/org.eclipse.edc.spi.system.ServiceExtension create mode 100644 sample/transfer/transfer-01-file-transfer/transfer-file-local/build.gradle.kts create mode 100644 sample/transfer/transfer-01-file-transfer/transfer-file-local/src/main/java/org/eclipse/edc/sample/extension/api/FileTransferDataSink.java create mode 100644 sample/transfer/transfer-01-file-transfer/transfer-file-local/src/main/java/org/eclipse/edc/sample/extension/api/FileTransferDataSinkFactory.java create mode 100644 sample/transfer/transfer-01-file-transfer/transfer-file-local/src/main/java/org/eclipse/edc/sample/extension/api/FileTransferDataSource.java create mode 100644 sample/transfer/transfer-01-file-transfer/transfer-file-local/src/main/java/org/eclipse/edc/sample/extension/api/FileTransferDataSourceFactory.java create mode 100644 sample/transfer/transfer-01-file-transfer/transfer-file-local/src/main/java/org/eclipse/edc/sample/extension/api/FileTransferExtension.java create mode 100644 sample/transfer/transfer-01-file-transfer/transfer-file-local/src/main/resources/META-INF/services/org.eclipse.edc.spi.system.ServiceExtension diff --git a/.idea/.gitignore b/.idea/.gitignore new file mode 100644 index 0000000..13566b8 --- /dev/null +++ b/.idea/.gitignore @@ -0,0 +1,8 @@ +# Default ignored files +/shelf/ +/workspace.xml +# Editor-based HTTP Client requests +/httpRequests/ +# Datasource local storage ignored files +/dataSources/ +/dataSources.local.xml diff --git a/.idea/gradle.xml b/.idea/gradle.xml new file mode 100644 index 0000000..fa7440c --- /dev/null +++ b/.idea/gradle.xml @@ -0,0 +1,27 @@ +<?xml version="1.0" encoding="UTF-8"?> +<project version="4"> + <component name="GradleMigrationSettings" migrationVersion="1" /> + <component name="GradleSettings"> + <option name="linkedExternalProjectsSettings"> + <GradleProjectSettings> + <option name="delegatedBuild" value="true" /> + <option name="testRunner" value="GRADLE" /> + <option name="distributionType" value="DEFAULT_WRAPPED" /> + <option name="externalProjectPath" value="$PROJECT_DIR$" /> + <option name="modules"> + <set> + <option value="$PROJECT_DIR$" /> + <option value="$PROJECT_DIR$/metadata-extractor" /> + <option value="$PROJECT_DIR$/sample" /> + <option value="$PROJECT_DIR$/sample/transfer" /> + <option value="$PROJECT_DIR$/sample/transfer/transfer-01-file-transfer" /> + <option value="$PROJECT_DIR$/sample/transfer/transfer-01-file-transfer/file-transfer-consumer" /> + <option value="$PROJECT_DIR$/sample/transfer/transfer-01-file-transfer/file-transfer-provider" /> + <option value="$PROJECT_DIR$/sample/transfer/transfer-01-file-transfer/status-checker" /> + <option value="$PROJECT_DIR$/sample/transfer/transfer-01-file-transfer/transfer-file-local" /> + </set> + </option> + </GradleProjectSettings> + </option> + </component> +</project> \ No newline at end of file diff --git a/.idea/misc.xml b/.idea/misc.xml new file mode 100644 index 0000000..8917924 --- /dev/null +++ b/.idea/misc.xml @@ -0,0 +1,10 @@ +<?xml version="1.0" encoding="UTF-8"?> +<project version="4"> + <component name="ExternalStorageConfigurationManager" enabled="true" /> + <component name="FrameworkDetectionExcludesConfiguration"> + <file type="web" url="file://$PROJECT_DIR$" /> + </component> + <component name="ProjectRootManager" version="2" languageLevel="JDK_17" project-jdk-name="19" project-jdk-type="JavaSDK"> + <output url="file://$PROJECT_DIR$/out" /> + </component> +</project> \ No newline at end of file diff --git a/.idea/uiDesigner.xml b/.idea/uiDesigner.xml new file mode 100644 index 0000000..2b63946 --- /dev/null +++ b/.idea/uiDesigner.xml @@ -0,0 +1,124 @@ +<?xml version="1.0" encoding="UTF-8"?> +<project version="4"> + <component name="Palette2"> + <group name="Swing"> + <item class="com.intellij.uiDesigner.HSpacer" tooltip-text="Horizontal Spacer" icon="/com/intellij/uiDesigner/icons/hspacer.svg" removable="false" auto-create-binding="false" can-attach-label="false"> + <default-constraints vsize-policy="1" hsize-policy="6" anchor="0" fill="1" /> + </item> + <item class="com.intellij.uiDesigner.VSpacer" tooltip-text="Vertical Spacer" icon="/com/intellij/uiDesigner/icons/vspacer.svg" removable="false" auto-create-binding="false" can-attach-label="false"> + <default-constraints vsize-policy="6" hsize-policy="1" anchor="0" fill="2" /> + </item> + <item class="javax.swing.JPanel" icon="/com/intellij/uiDesigner/icons/panel.svg" removable="false" auto-create-binding="false" can-attach-label="false"> + <default-constraints vsize-policy="3" hsize-policy="3" anchor="0" fill="3" /> + </item> + <item class="javax.swing.JScrollPane" icon="/com/intellij/uiDesigner/icons/scrollPane.svg" removable="false" auto-create-binding="false" can-attach-label="true"> + <default-constraints vsize-policy="7" hsize-policy="7" anchor="0" fill="3" /> + </item> + <item class="javax.swing.JButton" icon="/com/intellij/uiDesigner/icons/button.svg" removable="false" auto-create-binding="true" can-attach-label="false"> + <default-constraints vsize-policy="0" hsize-policy="3" anchor="0" fill="1" /> + <initial-values> + <property name="text" value="Button" /> + </initial-values> + </item> + <item class="javax.swing.JRadioButton" icon="/com/intellij/uiDesigner/icons/radioButton.svg" removable="false" auto-create-binding="true" can-attach-label="false"> + <default-constraints vsize-policy="0" hsize-policy="3" anchor="8" fill="0" /> + <initial-values> + <property name="text" value="RadioButton" /> + </initial-values> + </item> + <item class="javax.swing.JCheckBox" icon="/com/intellij/uiDesigner/icons/checkBox.svg" removable="false" auto-create-binding="true" can-attach-label="false"> + <default-constraints vsize-policy="0" hsize-policy="3" anchor="8" fill="0" /> + <initial-values> + <property name="text" value="CheckBox" /> + </initial-values> + </item> + <item class="javax.swing.JLabel" icon="/com/intellij/uiDesigner/icons/label.svg" removable="false" auto-create-binding="false" can-attach-label="false"> + <default-constraints vsize-policy="0" hsize-policy="0" anchor="8" fill="0" /> + <initial-values> + <property name="text" value="Label" /> + </initial-values> + </item> + <item class="javax.swing.JTextField" icon="/com/intellij/uiDesigner/icons/textField.svg" removable="false" auto-create-binding="true" can-attach-label="true"> + <default-constraints vsize-policy="0" hsize-policy="6" anchor="8" fill="1"> + <preferred-size width="150" height="-1" /> + </default-constraints> + </item> + <item class="javax.swing.JPasswordField" icon="/com/intellij/uiDesigner/icons/passwordField.svg" removable="false" auto-create-binding="true" can-attach-label="true"> + <default-constraints vsize-policy="0" hsize-policy="6" anchor="8" fill="1"> + <preferred-size width="150" height="-1" /> + </default-constraints> + </item> + <item class="javax.swing.JFormattedTextField" icon="/com/intellij/uiDesigner/icons/formattedTextField.svg" removable="false" auto-create-binding="true" can-attach-label="true"> + <default-constraints vsize-policy="0" hsize-policy="6" anchor="8" fill="1"> + <preferred-size width="150" height="-1" /> + </default-constraints> + </item> + <item class="javax.swing.JTextArea" icon="/com/intellij/uiDesigner/icons/textArea.svg" removable="false" auto-create-binding="true" can-attach-label="true"> + <default-constraints vsize-policy="6" hsize-policy="6" anchor="0" fill="3"> + <preferred-size width="150" height="50" /> + </default-constraints> + </item> + <item class="javax.swing.JTextPane" icon="/com/intellij/uiDesigner/icons/textPane.svg" removable="false" auto-create-binding="true" can-attach-label="true"> + <default-constraints vsize-policy="6" hsize-policy="6" anchor="0" fill="3"> + <preferred-size width="150" height="50" /> + </default-constraints> + </item> + <item class="javax.swing.JEditorPane" icon="/com/intellij/uiDesigner/icons/editorPane.svg" removable="false" auto-create-binding="true" can-attach-label="true"> + <default-constraints vsize-policy="6" hsize-policy="6" anchor="0" fill="3"> + <preferred-size width="150" height="50" /> + </default-constraints> + </item> + <item class="javax.swing.JComboBox" icon="/com/intellij/uiDesigner/icons/comboBox.svg" removable="false" auto-create-binding="true" can-attach-label="true"> + <default-constraints vsize-policy="0" hsize-policy="2" anchor="8" fill="1" /> + </item> + <item class="javax.swing.JTable" icon="/com/intellij/uiDesigner/icons/table.svg" removable="false" auto-create-binding="true" can-attach-label="false"> + <default-constraints vsize-policy="6" hsize-policy="6" anchor="0" fill="3"> + <preferred-size width="150" height="50" /> + </default-constraints> + </item> + <item class="javax.swing.JList" icon="/com/intellij/uiDesigner/icons/list.svg" removable="false" auto-create-binding="true" can-attach-label="false"> + <default-constraints vsize-policy="6" hsize-policy="2" anchor="0" fill="3"> + <preferred-size width="150" height="50" /> + </default-constraints> + </item> + <item class="javax.swing.JTree" icon="/com/intellij/uiDesigner/icons/tree.svg" removable="false" auto-create-binding="true" can-attach-label="false"> + <default-constraints vsize-policy="6" hsize-policy="6" anchor="0" fill="3"> + <preferred-size width="150" height="50" /> + </default-constraints> + </item> + <item class="javax.swing.JTabbedPane" icon="/com/intellij/uiDesigner/icons/tabbedPane.svg" removable="false" auto-create-binding="true" can-attach-label="false"> + <default-constraints vsize-policy="3" hsize-policy="3" anchor="0" fill="3"> + <preferred-size width="200" height="200" /> + </default-constraints> + </item> + <item class="javax.swing.JSplitPane" icon="/com/intellij/uiDesigner/icons/splitPane.svg" removable="false" auto-create-binding="false" can-attach-label="false"> + <default-constraints vsize-policy="3" hsize-policy="3" anchor="0" fill="3"> + <preferred-size width="200" height="200" /> + </default-constraints> + </item> + <item class="javax.swing.JSpinner" icon="/com/intellij/uiDesigner/icons/spinner.svg" removable="false" auto-create-binding="true" can-attach-label="true"> + <default-constraints vsize-policy="0" hsize-policy="6" anchor="8" fill="1" /> + </item> + <item class="javax.swing.JSlider" icon="/com/intellij/uiDesigner/icons/slider.svg" removable="false" auto-create-binding="true" can-attach-label="false"> + <default-constraints vsize-policy="0" hsize-policy="6" anchor="8" fill="1" /> + </item> + <item class="javax.swing.JSeparator" icon="/com/intellij/uiDesigner/icons/separator.svg" removable="false" auto-create-binding="false" can-attach-label="false"> + <default-constraints vsize-policy="6" hsize-policy="6" anchor="0" fill="3" /> + </item> + <item class="javax.swing.JProgressBar" icon="/com/intellij/uiDesigner/icons/progressbar.svg" removable="false" auto-create-binding="true" can-attach-label="false"> + <default-constraints vsize-policy="0" hsize-policy="6" anchor="0" fill="1" /> + </item> + <item class="javax.swing.JToolBar" icon="/com/intellij/uiDesigner/icons/toolbar.svg" removable="false" auto-create-binding="false" can-attach-label="false"> + <default-constraints vsize-policy="0" hsize-policy="6" anchor="0" fill="1"> + <preferred-size width="-1" height="20" /> + </default-constraints> + </item> + <item class="javax.swing.JToolBar$Separator" icon="/com/intellij/uiDesigner/icons/toolbarSeparator.svg" removable="false" auto-create-binding="false" can-attach-label="false"> + <default-constraints vsize-policy="0" hsize-policy="0" anchor="0" fill="1" /> + </item> + <item class="javax.swing.JScrollBar" icon="/com/intellij/uiDesigner/icons/scrollbar.svg" removable="false" auto-create-binding="true" can-attach-label="false"> + <default-constraints vsize-policy="6" hsize-policy="0" anchor="0" fill="2" /> + </item> + </group> + </component> +</project> \ No newline at end of file diff --git a/.idea/vcs.xml b/.idea/vcs.xml new file mode 100644 index 0000000..94a25f7 --- /dev/null +++ b/.idea/vcs.xml @@ -0,0 +1,6 @@ +<?xml version="1.0" encoding="UTF-8"?> +<project version="4"> + <component name="VcsDirectoryMappings"> + <mapping directory="$PROJECT_DIR$" vcs="Git" /> + </component> +</project> \ No newline at end of file diff --git a/sample/transfer/README.md b/sample/transfer/README.md new file mode 100644 index 0000000..a6e2b52 --- /dev/null +++ b/sample/transfer/README.md @@ -0,0 +1,62 @@ +# Transfer samples + +The samples in this scope revolve around the topic of transferring data between two connectors. Here +you will learn about the steps required for a transfer on provider as well as consumer side. The +samples start with the simple example of a local file transfer and then show different ways to tweak +that transfer, before a transfer is performed between different cloud providers. + +> Before starting with these samples, be sure to check out the [basic samples](../basic/README.md)! + +## Samples + +### [Transfer sample 01](./transfer-01-file-transfer/README.md): Perform a local file transfer + +In this sample you will perform your first data transfer. To keep it simple, a file is transferred +on your local machine from one directory to another. You will see which extensions and +configurations are required for a transfer and learn +how to create a data offer as a provider as well as which steps to perform as a consumer. + +### [Transfer sample 02](./transfer-02-file-transfer-listener/README.md): Implement a transfer listener + +As you'll learn in the first transfer sample, the process of a data transfer is executed in a state +machine and runs asynchronously in the background after being initiated. This sample is an +enhancement of the previous sample and shows how a listener can be used to immediately react to +state changes in this asynchronous process. + +### [Transfer sample 03](./transfer-03-modify-transferprocess/README.md): Modify a TransferProcess + +This sample is another enhancement of the first transfer sample. After you've learned how to react +to state changes during a data transfer, here you will see how to actively modify a process in the +state machine in a thread-safe manner. + +### [Transfer sample 04](./transfer-04-open-telemetry/README.md): Open Telemetry + +Now that you've gotten familiar with the process of transferring data, this sample will show +how `OpenTelemetry`,`Jaeger`, `Prometheus` and `Micrometer` can be used to collect and visualize +traces and metrics during this process. + +### [Transfer sample 05](./transfer-05-file-transfer-cloud/README.md): Perform a file transfer between cloud providers + +While performing a local file transfer is a simple and thereby good first transfer example, you will +likely never encounter this in a real-world scenario. So now we'll move on to a more complex +transfer scenario, where a file is transferred not in the local file system, but between two +different cloud providers. In this sample you will set up +a provider that offers a file located in an `Azure Blob Storage`, and a consumer that requests to +transfer this file to an `AWS S3 bucket`. Terraform is used for creating all required cloud +resources. + +### [Transfer sample 06](./transfer-06-consumer-pull-http/README.md): Perform a consumer pull exchange between a consumer and a provider + +In this sample, we will describe a step-by-step guide to demonstrate a consumer pull exchange +between two connections. One connecter is a consumer and the other is a provider. The consumer will +initiate a transfer, and the provider will send an EndpointDataReference to the consumer. Finally, +the consumer will be able to access the data by requesting the endpoint that received through the +EndpointDataReference. + +### [Transfer sample 07](./transfer-07-provider-push-http/README.md): Perform a provider push exchange between a consumer and a provider + +In this sample, we will describe a step-by-step guide to demonstrate a provider push exchange +between two connections. One connecter is a consumer and the other is a provider. The consumer will +initiate the transfer by sending a DataRequest with any destination type other +than HttpProxy, and the provider will fetch the date from the actual DataSource and push it to the +consumer. diff --git a/sample/transfer/transfer-01-file-transfer/README.md b/sample/transfer/transfer-01-file-transfer/README.md new file mode 100644 index 0000000..7bccaa0 --- /dev/null +++ b/sample/transfer/transfer-01-file-transfer/README.md @@ -0,0 +1,307 @@ +# Implement a simple file transfer + +After successfully providing custom configuration properties to the EDC, we will perform a data transfer next: transmit +a test file from one connector to another connector. We want to keep things simple, so we will run both connectors on +the same physical machine (i.e. your development machine) and the file is transferred from one folder in the file system +to another folder. It is not difficult to imagine that instead of the local file system, the transfer happens between +more sophisticated storage locations, like a database or a cloud storage. + +This is quite a big step up from the previous sample, where we ran only one connector. Those are the concrete tasks: + +* Creating an additional connector, so that in the end we have two connectors, a consumer and a provider +* Providing communication between provider and consumer using IDS multipart messages +* Utilizing the management API to interact with the connector system +* Performing a contract negotiation between provider and consumer +* Performing a file transfer + * The consumer will initiate a file transfer + * The provider will fulfill that request and copy a file to the desired location + +Also, in order to keep things organized, the code in this example has been separated into several Java modules: + +* `file-transfer-[consumer|provider]`: contains the configuration and build files for both the consumer and the provider connector +* `transfer-file-local`: contains all the code necessary for the file transfer, integrated on provider side +* `status-checker`: contains the code for checking if the file has been transfer, integrated on the consumer side + +## Create the file transfer extension + +The provider connector needs to transfer a file to the location specified by the consumer connector when the data is +requested. In order to offer any data, the provider must maintain an internal list of assets that are available for +transfer, the so-called "catalog". For the sake of simplicity we use an in-memory catalog and pre-fill it with just one +single class. The provider also needs to create a contract offer for the asset, based on which a contract agreement can +be negotiated. For this, we also use an in-memory store and add a single contract definition that is valid for the +asset. + +```java +// in FileTransferExtension.java +@Override +public void initialize(ServiceExtensionContext context){ + // ... + var policy = createPolicy(); + policyStore.save(policy); + + registerDataEntries(context); + registerContractDefinition(policy.getUid()); + // ... +} + +//... + +private void registerDataEntries(ServiceExtensionContext context) { + var assetPathSetting = context.getSetting(EDC_ASSET_PATH, "/tmp/provider/test-document.txt"); + var assetPath = Path.of(assetPathSetting); + + var dataAddress = DataAddress.Builder.newInstance() + .property("type", "File") + .property("path", assetPath.getParent().toString()) + .property("filename", assetPath.getFileName().toString()) + .build(); + + var assetId = "test-document"; + var asset = Asset.Builder.newInstance().id(assetId).build(); + + assetIndex.create(asset, dataAddress); + } + +private void registerContractDefinition(String uid) { + var contractDefinition = ContractDefinition.Builder.newInstance() + .id("1") + .accessPolicyId(uid) + .contractPolicyId(uid) + .assetsSelectorCriterion(criterion(Asset.PROPERTY_ID, "=", "1")) + .whenEquals(Asset.PROPERTY_ID, "test-document") + .build()) + .build(); + + contractStore.save(contractDefinition); + } +``` + +This adds an `Asset` to the `AssetIndex` and the relative `DataAddress` to the `DataAddressResolver`. +Or, in other words, your provider now "hosts" one file named `test-document.txt` located in the path +configured by the setting `edc.samples.transfer.01.asset.path` on your development machine. It makes it available for +transfer under its `id` `"test-document"`. While it makes sense to have some sort of similarity between file name and +id, it is by no means mandatory. + +It also adds a `ContractDefinition` with `id` `1` and a previously created `Policy` (code omitted above), that poses no +restrictions on the data usage. The `ContractDefinition` also has an `assetsSelector` `Criterion` defining that it is +valid for all assets with the `id` `test-document`. Thus, it is valid for the created asset. + +Next to offering the file, the provider also needs to be able to transfer the file. Therefore, the `transfer-file` +module also provides the code for copying the file to a specified location (code omitted here for brevity). It contains +the [FileTransferDataSource](transfer-file-local/src/main/java/org/eclipse/edc/sample/extension/api/FileTransferDataSource.java) +and the [FileTransferDataSink](transfer-file-local/src/main/java/org/eclipse/edc/sample/extension/api/FileTransferDataSink.java) +as well as respective factories for both. The factories are registered with the `PipelineService` in the +[FileTransferExtension](transfer-file-local/src/main/java/org/eclipse/edc/sample/extension/api/FileTransferExtension.java), +thus making them available when a data request is processed. + +## Create the status checker extension + +The consumer needs to know when the file transfer has been completed. For doing that, in the extension +we are going to implement a custom `StatusChecker` that will be registered with the `StatusCheckerRegistry` in the +[SampleStatusCheckerExtension](status-checker/src/main/java/org/eclipse/edc/sample/extension/checker/SampleStatusCheckerExtension.java) +The custom status checker will handle the check for the destination type `File` and it will check that the path +specified in the data requests exists. The code is available in the +class [SampleFileStatusChecker](status-checker/src/main/java/org/eclipse/edc/sample/extension/checker/SampleFileStatusChecker.java) + +## Create the connectors + +After creating the required extensions, we next need to create the two connectors. For both of them we need a gradle +build file and a config file. Common dependencies we need to add to the build files on both sides are the following: + +```kotlin +// in file-transfer-consumer/build.gradle.kts and file-transfer-provider/build.gradle.kts: +implementation(libs.edc.configuration.filesystem) + +implementation(libs.edc.dsp) +implementation(libs.edc.iam.mock) + +implementation(libs.edc.management.api) +implementation(libs.edc.auth.tokenbased) +``` + +Three of these dependencies are new and have not been used in the previous samples: +1. `data-protocols:ids`: contains all IDS modules and therefore enables IDS Multipart communication with other connectors +2. `extensions:iam:iam-mock`: provides a no-op identity provider, which does not require certificates and performs no checks +3. `extensions:api:auth-tokenbased`: adds authentication for management API endpoints + +### Provider connector + +As the provider connector is the one performing the file transfer after the file has been requested by the consumer, it +needs the `transfer-file-local` extension provided in this sample. + +```kotlin +implementation(project(":transfer:transfer-01-file-transfer:transfer-file-local")) +``` + +We also need to adjust the provider's `config.properties`. The property `edc.samples.transfer.01.asset.path` should +point to an existing file in our local environment, as this is the file that will be transferred. We also configure a +separate API context for the management API, like we learned in previous chapter. Then we add the property +`edc.dsp.callback.address`, which should point to our provider connector's IDS address. This is used as the callback +address during the contract negotiation. Since the DSP API is running on a different port (default is `8282`), we set +the webhook address to `http://localhost:8282/protocol` accordingly. + +### Consumer connector + +The consumer is the one "requesting" the data and providing a destination for it, i.e. a directory into which the +provider can copy the requested file. + +We configure the consumer's API ports in `consumer/config.properties`, so that it does not use the same ports as the +provider. In the config file, we also need to configure the API key authentication, as we're going to use +endpoints from the EDC's management API in this sample and integrated the extension for token-based API +authentication. Therefore, we add the property `edc.api.auth.key` and set it to e.g. `password`. And last, we also need +to configure the consumer's webhook address. We expose the IDS API endpoints on a different port and path than other +endpoints, so the property `edc.dsp.callback.address` is adjusted to match the IDS API port. + +The consumer connector also needs the `status-checker` extension for marking the transfer as completed on the consumer +side. + +```kotlin +implementation(project(":transfer:transfer-01-file-transfer:status-checker")) +``` + +## Run the sample + +Running this sample consists of multiple steps, that are executed one by one. + +### 1. Build and start the connectors + +The first step to running this sample is building and starting both the provider and the consumer connector. This is +done the same way as in the previous samples. + +```bash +./gradlew transfer:transfer-01-file-transfer:file-transfer-consumer:build +java -Dedc.fs.config=transfer/transfer-01-file-transfer/file-transfer-consumer/config.properties -jar transfer/transfer-01-file-transfer/file-transfer-consumer/build/libs/consumer.jar +# in another terminal window: +./gradlew transfer:transfer-01-file-transfer:file-transfer-provider:build +java -Dedc.fs.config=transfer/transfer-01-file-transfer/file-transfer-provider/config.properties -jar transfer/transfer-01-file-transfer/file-transfer-provider/build/libs/provider.jar +```` + +Assuming you didn't change the ports in config files, the consumer will listen on the ports `9191`, `9192` +(management API) and `9292` (PROTOCOL API) and the provider will listen on the ports `8181`, `8182` +(management API) and `8282` (PROTOCOL API). + +### 2. Initiate a contract negotiation + +In order to request any data, a contract agreement has to be negotiated between provider and consumer. The provider +offers all of their assets in the form of contract offers, which are the basis for such a negotiation. In the +`transfer-file-local` extension, we've added a contract definition (from which contract offers can be created) for the +file, but the consumer has yet to accept this offer. + +The consumer now needs to initiate a contract negotiation sequence with the provider. That sequence looks as follows: + +1. Consumer sends a contract offer to the provider (__currently, this has to be equal to the provider's offer!__) +2. Provider validates the received offer against its own offer +3. Provider either sends an agreement or a rejection, depending on the validation result +4. In case of successful validation, provider and consumer store the received agreement for later reference + +Of course, this is the simplest possible negotiation sequence. Later on, both connectors can also send counter offers in +addition to just confirming or declining an offer. + +In order to trigger the negotiation, we use a management API endpoint. We set our contract offer in the request +body. The contract offer is prepared in [contractoffer.json](contractoffer.json) and can be used as is. In a real +scenario, a potential consumer would first need to request a description of the provider's offers in order to get the +provider's contract offer. + +> Note, that we need to specify the `X-Api-Key` header, as we integrated token-based API authentication. The value +of the header has to match the value of the `edc.api.auth.key` property in the consumer's `config.properties`. + +```bash +curl -X POST -H "Content-Type: application/json" -H "X-Api-Key: password" -d @transfer/transfer-01-file-transfer/contractoffer.json "http://localhost:9192/management/v2/contractnegotiations" +``` + +In the response we'll get a UUID that we can use to get the contract agreement negotiated between provider and consumer. + +Sample output: + +```json +{"@id":"5a6b7e22-dc7d-4135-bc98-4cc5fd1dd1ed"} +``` + +### 3. Look up the contract agreement ID + +After calling the endpoint for initiating a contract negotiation, we get a UUID as the response. This UUID is the ID of +the ongoing contract negotiation between consumer and provider. The negotiation sequence between provider and consumer +is executed asynchronously in the background by a state machine. Once both provider and consumer either reach the +`confirmed` or the `declined` state, the negotiation is finished. We can now use the UUID to check the current status +of the negotiation using an endpoint on the consumer side. Again, we use the `X-Api-Key` header with the same value +that's set in our consumer's `config.properties`. + +```bash +curl -X GET -H 'X-Api-Key: password' "http://localhost:9192/management/v2/contractnegotiations/{UUID}" +``` + +This will return information about the negotiation, which contains e.g. the current state of the negotiation and, if the +negotiation has been completed successfully, the ID of a contract agreement. We can now use this agreement to request +the file. So we copy and store the agreement ID for the next step. + +Sample output: + +```json +{ + ... + "edc:contractAgreementId":"1:test-document:fb80be14-8e09-4e50-b65d-c269bc1f16d0", + "edc:state":"FINALIZED", + ... +} +``` + +If you see an output similar to the following, the negotiation has not yet been completed. In this case, +just wait for a moment and call the endpoint again. + +```json +{ + ... + "edc:state": "REQUESTED", + "edc:contractAgreementId": null, + ... +} +``` + +### 4. Request the file + +Now that we have a contract agreement, we can finally request the file. In the request body we need to specify +which asset we want transferred, the ID of the contract agreement, the address of the provider connector and where +we want the file transferred. The request body is prepared in [filetransfer.json](filetransfer.json). Before executing +the request, insert the contract agreement ID from the previous step and adjust the destination location for the file +transfer. Then run: + +```bash +curl -X POST -H "Content-Type: application/json" -H "X-Api-Key: password" -d @transfer/transfer-01-file-transfer/filetransfer.json "http://localhost:9192/management/v2/transferprocesses" +``` + +Again, we will get a UUID in the response. This time, this is the ID of the `TransferProcess` created on the consumer +side, because like the contract negotiation, the data transfer is handled in a state machine and performed asynchronously. + +Sample output: + +```json +{"@id":"deeed974-8a43-4fd5-93ad-e1b8c26bfa44"} +``` + +Since transferring a file does not require any resource provisioning on either side, the transfer will be very quick and +most likely already done by the time you read the UUID. + +--- + +You can also check the logs of the connectors to see that the transfer has been completed: + +Consumer side: + +```bash +DEBUG 2022-05-03T10:37:59.599642754 Starting transfer for asset asset-id +DEBUG 2022-05-03T10:37:59.6071347 Transfer process initialised f925131b-d61e-48b9-aa15-0f5e2e749064 +DEBUG 2022-05-03T10:38:01.230902645 TransferProcessManager: Sending process f925131b-d61e-48b9-aa15-0f5e2e749064 request to http://localhost:8282/protocol +DEBUG 2022-05-03T10:38:01.260916372 Response received from connector. Status 200 +DEBUG 2022-05-03T10:38:01.285641788 TransferProcessManager: Process f925131b-d61e-48b9-aa15-0f5e2e749064 is now REQUESTED +DEBUG 2022-05-03T10:38:06.246094874 Process f925131b-d61e-48b9-aa15-0f5e2e749064 is now IN_PROGRESS +DEBUG 2022-05-03T10:38:06.246755642 Process f925131b-d61e-48b9-aa15-0f5e2e749064 is now COMPLETED +``` + +### 5. See transferred file + +After the file transfer is completed, we can check the destination path specified in the request for the file. Here, +we'll now find a file with the same content as the original file offered by the provider. + +--- + +[Next Chapter](../transfer-02-file-transfer-listener/README.md) diff --git a/sample/transfer/transfer-01-file-transfer/contractoffer.json b/sample/transfer/transfer-01-file-transfer/contractoffer.json new file mode 100644 index 0000000..fbad5ef --- /dev/null +++ b/sample/transfer/transfer-01-file-transfer/contractoffer.json @@ -0,0 +1,24 @@ +{ + "@context": { + "edc": "https://w3id.org/edc/v0.0.1/ns/" + }, + "@type": "NegotiationInitiateRequestDto", + "connectorId": "provider", + "consumerId": "consumer", + "providerId": "provider", + "connectorAddress": "http://localhost:8282/protocol", + "protocol": "dataspace-protocol-http", + "offer": { + "offerId": "1:test-document:3a75736e-001d-4364-8bd4-9888490edb58", + "assetId": "test-document", + "policy": { + "@context": "http://www.w3.org/ns/odrl.jsonld", + "@id": "1:test-document:13dce0f1-52ed-4554-a194-e83e92733ee5", + "@type": "set", + "permission": [], + "prohibition": [], + "obligation": [], + "target": "test-document" + } + } +} diff --git a/sample/transfer/transfer-01-file-transfer/file-transfer-consumer/build.gradle.kts b/sample/transfer/transfer-01-file-transfer/file-transfer-consumer/build.gradle.kts new file mode 100644 index 0000000..c9be93f --- /dev/null +++ b/sample/transfer/transfer-01-file-transfer/file-transfer-consumer/build.gradle.kts @@ -0,0 +1,49 @@ +/* + * Copyright (c) 2020, 2021 Microsoft Corporation + * + * 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: + * Microsoft Corporation - initial API and implementation + * Fraunhofer Institute for Software and Systems Engineering - added dependencies + * ZF Friedrichshafen AG - add dependency + * + */ + +plugins { + `java-library` + id("application") + id("com.github.johnrengelman.shadow") version "7.1.2" +} + +dependencies { + implementation(libs.edc.control.plane.core) + implementation(libs.edc.data.plane.selector.core) + + implementation(libs.edc.api.observability) + + implementation(libs.edc.configuration.filesystem) + implementation(libs.edc.iam.mock) + + implementation(libs.edc.auth.tokenbased) + implementation(libs.edc.management.api) + + implementation(libs.edc.dsp) + + implementation(project(":sample:transfer:transfer-01-file-transfer:status-checker")) + +} + +application { + mainClass.set("org.eclipse.edc.boot.system.runtime.BaseRuntime") +} + +tasks.withType<com.github.jengelman.gradle.plugins.shadow.tasks.ShadowJar> { + exclude("**/pom.properties", "**/pom.xm") + mergeServiceFiles() + archiveFileName.set("consumer.jar") +} diff --git a/sample/transfer/transfer-01-file-transfer/file-transfer-consumer/config.properties b/sample/transfer/transfer-01-file-transfer/file-transfer-consumer/config.properties new file mode 100644 index 0000000..3235fa7 --- /dev/null +++ b/sample/transfer/transfer-01-file-transfer/file-transfer-consumer/config.properties @@ -0,0 +1,13 @@ +web.http.port=9191 +web.http.path=/api +web.http.management.port=9192 +web.http.management.path=/management +web.http.protocol.port=9292 +web.http.protocol.path=/protocol + +edc.api.auth.key=password +edc.dsp.callback.address=http://localhost:9292/protocol +edc.participant.id=consumer +edc.ids.id=urn:connector:consumer +edc.jsonld.http.enabled=true +edc.jsonld.https.enabled=true diff --git a/sample/transfer/transfer-01-file-transfer/file-transfer-provider/build.gradle.kts b/sample/transfer/transfer-01-file-transfer/file-transfer-provider/build.gradle.kts new file mode 100644 index 0000000..01da0f0 --- /dev/null +++ b/sample/transfer/transfer-01-file-transfer/file-transfer-provider/build.gradle.kts @@ -0,0 +1,44 @@ +/* + * Copyright (c) 2020, 2021 Microsoft Corporation + * + * 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: + * Microsoft Corporation - initial API and implementation + * Fraunhofer Institute for Software and Systems Engineering - added dependencies + * ZF Friedrichshafen AG - add dependency + * + */ + +plugins { + `java-library` + id("application") + id("com.github.johnrengelman.shadow") version "7.1.2" +} + +dependencies { + implementation(libs.edc.control.plane.core) + implementation(libs.edc.data.plane.selector.core) + implementation(libs.edc.api.observability) + implementation(libs.edc.configuration.filesystem) + implementation(libs.edc.iam.mock) + implementation(libs.edc.auth.tokenbased) + implementation(libs.edc.management.api) + implementation(libs.edc.dsp) + + implementation(project(":sample:transfer:transfer-01-file-transfer:transfer-file-local")) +} + +application { + mainClass.set("org.eclipse.edc.boot.system.runtime.BaseRuntime") +} + +tasks.withType<com.github.jengelman.gradle.plugins.shadow.tasks.ShadowJar> { + exclude("**/pom.properties", "**/pom.xm") + mergeServiceFiles() + archiveFileName.set("provider.jar") +} diff --git a/sample/transfer/transfer-01-file-transfer/file-transfer-provider/config.properties b/sample/transfer/transfer-01-file-transfer/file-transfer-provider/config.properties new file mode 100644 index 0000000..513ee66 --- /dev/null +++ b/sample/transfer/transfer-01-file-transfer/file-transfer-provider/config.properties @@ -0,0 +1,14 @@ +web.http.port=8181 +web.http.path=/api +web.http.management.port=8182 +web.http.management.path=/management +web.http.protocol.port=8282 +web.http.protocol.path=/protocol +edc.samples.transfer.01.asset.path=C:\\Users\\{user}\\Desktop\\english-test.pdf +edc.samples.transfer.01.asset.pid=https://doi.org/10.48550/arXiv.2302.12813 +edc.samples.transfer.01.asset.link=https://arxiv.org/pdf/2302.12813.pdf +edc.dsp.callback.address=http://localhost:8282/protocol +edc.participant.id=provider +edc.ids.id=urn:connector:provider +edc.jsonld.http.enabled=true +edc.jsonld.https.enabled=true diff --git a/sample/transfer/transfer-01-file-transfer/filetransfer.json b/sample/transfer/transfer-01-file-transfer/filetransfer.json new file mode 100644 index 0000000..d7b0025 --- /dev/null +++ b/sample/transfer/transfer-01-file-transfer/filetransfer.json @@ -0,0 +1,20 @@ +{ + "@context": { + "edc": "https://w3id.org/edc/v0.0.1/ns/" + }, + "@type": "TransferRequestDto", + "dataDestination": { + "@type": "DataAddress", + "type": "File", + "properties": { + "path": "C:\\Users\\{user}\\Desktop\\test-transfers_.pdf", + "keyName": "keyName" + } + }, + "protocol": "dataspace-protocol-http", + "assetId": "test-document", + "contractId": "1:test-document:b0d9583f-4bb9-4fc7-b4f4-10f43ce19507", + "connectorAddress": "http://localhost:8282/protocol", + "privateProperties": {}, + "managedResources": false +} diff --git a/sample/transfer/transfer-01-file-transfer/status-checker/build.gradle.kts b/sample/transfer/transfer-01-file-transfer/status-checker/build.gradle.kts new file mode 100644 index 0000000..9c3cc3c --- /dev/null +++ b/sample/transfer/transfer-01-file-transfer/status-checker/build.gradle.kts @@ -0,0 +1,21 @@ +/* + * Copyright (c) 2020-2022 Microsoft Corporation + * + * 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: + * Microsoft Corporation - initial API and implementation + * + */ + +plugins { + `java-library` +} + +dependencies { + api(libs.edc.control.plane.spi) +} diff --git a/sample/transfer/transfer-01-file-transfer/status-checker/src/main/java/org/eclipse/edc/sample/extension/checker/SampleFileStatusChecker.java b/sample/transfer/transfer-01-file-transfer/status-checker/src/main/java/org/eclipse/edc/sample/extension/checker/SampleFileStatusChecker.java new file mode 100644 index 0000000..a69fed4 --- /dev/null +++ b/sample/transfer/transfer-01-file-transfer/status-checker/src/main/java/org/eclipse/edc/sample/extension/checker/SampleFileStatusChecker.java @@ -0,0 +1,39 @@ +/* + * Copyright (c) 2020-2022 Microsoft Corporation + * + * 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: + * Microsoft Corporation - initial API and implementation + * + */ + +package org.eclipse.edc.sample.extension.checker; + +import org.eclipse.edc.connector.transfer.spi.types.ProvisionedResource; +import org.eclipse.edc.connector.transfer.spi.types.StatusChecker; +import org.eclipse.edc.connector.transfer.spi.types.TransferProcess; + +import java.nio.file.Files; +import java.nio.file.Paths; +import java.util.List; +import java.util.Optional; + +public class SampleFileStatusChecker implements StatusChecker { + @Override + public boolean isComplete(TransferProcess transferProcess, List<ProvisionedResource> resources) { + var destination = transferProcess.getDataRequest().getDataDestination(); + var path = destination.getProperty("path"); + return Optional.ofNullable(path) + .map(this::checkPath) + .orElse(false); + } + + private boolean checkPath(String path) { + return Files.exists(Paths.get(path)); + } +} diff --git a/sample/transfer/transfer-01-file-transfer/status-checker/src/main/java/org/eclipse/edc/sample/extension/checker/SampleStatusCheckerExtension.java b/sample/transfer/transfer-01-file-transfer/status-checker/src/main/java/org/eclipse/edc/sample/extension/checker/SampleStatusCheckerExtension.java new file mode 100644 index 0000000..32b630d --- /dev/null +++ b/sample/transfer/transfer-01-file-transfer/status-checker/src/main/java/org/eclipse/edc/sample/extension/checker/SampleStatusCheckerExtension.java @@ -0,0 +1,41 @@ +/* + * Copyright (c) 2020-2022 Microsoft Corporation + * + * 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: + * Microsoft Corporation - initial API and implementation + * + */ + +package org.eclipse.edc.sample.extension.checker; + +import org.eclipse.edc.connector.transfer.spi.status.StatusCheckerRegistry; +import org.eclipse.edc.runtime.metamodel.annotation.Extension; +import org.eclipse.edc.runtime.metamodel.annotation.Inject; +import org.eclipse.edc.spi.system.ServiceExtension; +import org.eclipse.edc.spi.system.ServiceExtensionContext; + +@Extension(value = SampleStatusCheckerExtension.NAME) +public class SampleStatusCheckerExtension implements ServiceExtension { + + public static final String NAME = "Sample status checker"; + + @Inject + private StatusCheckerRegistry checkerRegistry; + + @Override + public String name() { + return NAME; + } + + + @Override + public void initialize(ServiceExtensionContext context) { + checkerRegistry.register("File", new SampleFileStatusChecker()); + } +} diff --git a/sample/transfer/transfer-01-file-transfer/status-checker/src/main/resources/META-INF/services/org.eclipse.edc.spi.system.ServiceExtension b/sample/transfer/transfer-01-file-transfer/status-checker/src/main/resources/META-INF/services/org.eclipse.edc.spi.system.ServiceExtension new file mode 100644 index 0000000..9e20e17 --- /dev/null +++ b/sample/transfer/transfer-01-file-transfer/status-checker/src/main/resources/META-INF/services/org.eclipse.edc.spi.system.ServiceExtension @@ -0,0 +1,15 @@ +# +# Copyright (c) 2020-2022 Microsoft Corporation +# +# 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: +# Microsoft Corporation - initial API and implementation +# +# + +org.eclipse.edc.sample.extension.checker.SampleStatusCheckerExtension diff --git a/sample/transfer/transfer-01-file-transfer/transfer-file-local/build.gradle.kts b/sample/transfer/transfer-01-file-transfer/transfer-file-local/build.gradle.kts new file mode 100644 index 0000000..f59ecae --- /dev/null +++ b/sample/transfer/transfer-01-file-transfer/transfer-file-local/build.gradle.kts @@ -0,0 +1,33 @@ +/* + * Copyright (c) 2020, 2021 Microsoft Corporation + * + * 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: + * Microsoft Corporation - initial API and implementation + * Fraunhofer Institute for Software and Systems Engineering - added dependencies + * + */ + +plugins { + `java-library` + id("application") +} + +dependencies { + api(libs.edc.control.plane.spi) + api(libs.edc.data.plane.spi) + implementation(libs.edc.control.plane.core) + implementation(libs.edc.data.plane.core) + implementation(libs.edc.data.plane.util) + implementation(libs.edc.data.plane.client) + implementation(libs.edc.data.plane.selector.client) + implementation(libs.edc.data.plane.selector.core) + implementation(libs.edc.transfer.data.plane) + implementation(libs.opentelemetry.annotations) + implementation(project(":metadata-extractor")) +} diff --git a/sample/transfer/transfer-01-file-transfer/transfer-file-local/src/main/java/org/eclipse/edc/sample/extension/api/FileTransferDataSink.java b/sample/transfer/transfer-01-file-transfer/transfer-file-local/src/main/java/org/eclipse/edc/sample/extension/api/FileTransferDataSink.java new file mode 100644 index 0000000..1fb0b4d --- /dev/null +++ b/sample/transfer/transfer-01-file-transfer/transfer-file-local/src/main/java/org/eclipse/edc/sample/extension/api/FileTransferDataSink.java @@ -0,0 +1,82 @@ +/* + * Copyright (c) 2022 Microsoft Corporation + * + * 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: + * Microsoft Corporation - initial API and implementation + * + */ + +package org.eclipse.edc.sample.extension.api; + +import io.opentelemetry.extension.annotations.WithSpan; +import org.eclipse.edc.connector.dataplane.spi.pipeline.DataSource; +import org.eclipse.edc.connector.dataplane.spi.pipeline.StreamFailure; +import org.eclipse.edc.connector.dataplane.spi.pipeline.StreamResult; +import org.eclipse.edc.connector.dataplane.util.sink.ParallelSink; + +import java.io.File; +import java.io.FileOutputStream; +import java.util.List; +import java.util.Objects; + +import static java.lang.String.format; +import static org.eclipse.edc.connector.dataplane.spi.pipeline.StreamFailure.Reason.GENERAL_ERROR; + +class FileTransferDataSink extends ParallelSink { + private File file; + + @WithSpan + @Override + protected StreamResult<Void> transferParts(List<DataSource.Part> parts) { + for (DataSource.Part part : parts) { + var fileName = part.name(); + try (var input = part.openStream()) { + try (var output = new FileOutputStream(file)) { + try { + input.transferTo(output); + } catch (Exception e) { + return getTransferResult(e, "Error transferring file %s", fileName); + } + } catch (Exception e) { + return getTransferResult(e, "Error creating file %s", fileName); + } + } catch (Exception e) { + return getTransferResult(e, "Error reading file %s", fileName); + } + } + return StreamResult.success(); + } + + private StreamResult<Void> getTransferResult(Exception e, String logMessage, Object... args) { + var message = format(logMessage, args); + monitor.severe(message, e); + return StreamResult.failure(new StreamFailure(List.of(message), GENERAL_ERROR)); + } + + public static class Builder extends ParallelSink.Builder<Builder, FileTransferDataSink> { + + public static Builder newInstance() { + return new Builder(); + } + + public Builder file(File file) { + sink.file = file; + return this; + } + + @Override + protected void validate() { + Objects.requireNonNull(sink.file, "file"); + } + + private Builder() { + super(new FileTransferDataSink()); + } + } +} diff --git a/sample/transfer/transfer-01-file-transfer/transfer-file-local/src/main/java/org/eclipse/edc/sample/extension/api/FileTransferDataSinkFactory.java b/sample/transfer/transfer-01-file-transfer/transfer-file-local/src/main/java/org/eclipse/edc/sample/extension/api/FileTransferDataSinkFactory.java new file mode 100644 index 0000000..93b0472 --- /dev/null +++ b/sample/transfer/transfer-01-file-transfer/transfer-file-local/src/main/java/org/eclipse/edc/sample/extension/api/FileTransferDataSinkFactory.java @@ -0,0 +1,65 @@ +/* + * Copyright (c) 2022 Microsoft Corporation + * + * 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: + * Microsoft Corporation - initial API and implementation + * + */ + +package org.eclipse.edc.sample.extension.api; + +import org.eclipse.edc.connector.dataplane.spi.pipeline.DataSink; +import org.eclipse.edc.connector.dataplane.spi.pipeline.DataSinkFactory; +import org.eclipse.edc.spi.monitor.Monitor; +import org.eclipse.edc.spi.result.Result; +import org.eclipse.edc.spi.types.domain.transfer.DataFlowRequest; +import org.jetbrains.annotations.NotNull; + +import java.io.File; +import java.util.concurrent.ExecutorService; + +class FileTransferDataSinkFactory implements DataSinkFactory { + private final Monitor monitor; + private final ExecutorService executorService; + private final int partitionSize; + + FileTransferDataSinkFactory(Monitor monitor, ExecutorService executorService, int partitionSize) { + this.monitor = monitor; + this.executorService = executorService; + this.partitionSize = partitionSize; + } + + @Override + public boolean canHandle(DataFlowRequest request) { + return "file".equalsIgnoreCase(request.getDestinationDataAddress().getType()); + } + + @Override + public @NotNull Result<Boolean> validate(DataFlowRequest request) { + return Result.success(true); + } + + @Override + public DataSink createSink(DataFlowRequest request) { + var destination = request.getDestinationDataAddress(); + + // verify destination path + var path = destination.getProperty("path"); + // As this is a controlled test input below is to avoid path-injection warning by CodeQL + var destinationFile = new File(path.replaceAll("\\.", ".").replaceAll("/", "/")); + + return FileTransferDataSink.Builder.newInstance() + .file(destinationFile) + .requestId(request.getId()) + .partitionSize(partitionSize) + .executorService(executorService) + .monitor(monitor) + .build(); + } +} diff --git a/sample/transfer/transfer-01-file-transfer/transfer-file-local/src/main/java/org/eclipse/edc/sample/extension/api/FileTransferDataSource.java b/sample/transfer/transfer-01-file-transfer/transfer-file-local/src/main/java/org/eclipse/edc/sample/extension/api/FileTransferDataSource.java new file mode 100644 index 0000000..f946039 --- /dev/null +++ b/sample/transfer/transfer-01-file-transfer/transfer-file-local/src/main/java/org/eclipse/edc/sample/extension/api/FileTransferDataSource.java @@ -0,0 +1,54 @@ +/* + * Copyright (c) 2022 Microsoft Corporation + * + * 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: + * Microsoft Corporation - initial API and implementation + * + */ + +package org.eclipse.edc.sample.extension.api; + +import org.eclipse.edc.connector.dataplane.spi.pipeline.DataSource; +import org.eclipse.edc.connector.dataplane.spi.pipeline.StreamResult; +import org.eclipse.edc.spi.EdcException; + +import java.io.File; +import java.io.FileInputStream; +import java.io.FileNotFoundException; +import java.io.InputStream; +import java.util.stream.Stream; + +class FileTransferDataSource implements DataSource { + + private final File file; + + FileTransferDataSource(File file) { + this.file = file; + } + + @Override + public StreamResult<Stream<Part>> openPartStream() { + var part = new Part() { + @Override + public String name() { + return file.getName(); + } + + @Override + public InputStream openStream() { + try { + return new FileInputStream(file); + } catch (FileNotFoundException e) { + throw new EdcException(e); + } + } + }; + return StreamResult.success(Stream.of(part)); + } +} diff --git a/sample/transfer/transfer-01-file-transfer/transfer-file-local/src/main/java/org/eclipse/edc/sample/extension/api/FileTransferDataSourceFactory.java b/sample/transfer/transfer-01-file-transfer/transfer-file-local/src/main/java/org/eclipse/edc/sample/extension/api/FileTransferDataSourceFactory.java new file mode 100644 index 0000000..459cce6 --- /dev/null +++ b/sample/transfer/transfer-01-file-transfer/transfer-file-local/src/main/java/org/eclipse/edc/sample/extension/api/FileTransferDataSourceFactory.java @@ -0,0 +1,58 @@ +/* + * Copyright (c) 2022 Microsoft Corporation + * + * 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: + * Microsoft Corporation - initial API and implementation + * + */ + +package org.eclipse.edc.sample.extension.api; + +import org.eclipse.edc.connector.dataplane.spi.pipeline.DataSource; +import org.eclipse.edc.connector.dataplane.spi.pipeline.DataSourceFactory; +import org.eclipse.edc.spi.result.Result; +import org.eclipse.edc.spi.types.domain.transfer.DataFlowRequest; +import org.jetbrains.annotations.NotNull; + +import java.io.File; + +class FileTransferDataSourceFactory implements DataSourceFactory { + @Override + public boolean canHandle(DataFlowRequest dataRequest) { + return "file".equalsIgnoreCase(dataRequest.getSourceDataAddress().getType()); + } + + @Override + public @NotNull Result<Boolean> validate(DataFlowRequest request) { + var source = getFile(request); + if (!source.exists()) { + return Result.failure("Source file " + source.getName() + " does not exist!"); + } + + return Result.success(true); + } + + @Override + public DataSource createSource(DataFlowRequest request) { + var source = getFile(request); + return new FileTransferDataSource(source); + } + + @NotNull + private File getFile(DataFlowRequest request) { + var dataAddress = request.getSourceDataAddress(); + // verify source path + var sourceFileName = dataAddress.getProperty("filename"); + var path = dataAddress.getProperty("path"); + // As this is a controlled test input below is to avoid path-injection warning by CodeQL + sourceFileName = sourceFileName.replaceAll("\\.", ".").replaceAll("/", "/"); + path = path.replaceAll("\\.", ".").replaceAll("/", "/"); + return new File(path, sourceFileName); + } +} diff --git a/sample/transfer/transfer-01-file-transfer/transfer-file-local/src/main/java/org/eclipse/edc/sample/extension/api/FileTransferExtension.java b/sample/transfer/transfer-01-file-transfer/transfer-file-local/src/main/java/org/eclipse/edc/sample/extension/api/FileTransferExtension.java new file mode 100644 index 0000000..ba94828 --- /dev/null +++ b/sample/transfer/transfer-01-file-transfer/transfer-file-local/src/main/java/org/eclipse/edc/sample/extension/api/FileTransferExtension.java @@ -0,0 +1,122 @@ +/* + * Copyright (c) 2021 Fraunhofer Institute for Software and Systems Engineering + * + * 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: + * Fraunhofer Institute for Software and Systems Engineering - initial API and implementation + * + */ + +package org.eclipse.edc.sample.extension.api; + +import org.eclipse.edc.connector.contract.spi.offer.store.ContractDefinitionStore; +import org.eclipse.edc.connector.contract.spi.types.offer.ContractDefinition; +import org.eclipse.edc.connector.dataplane.spi.pipeline.DataTransferExecutorServiceContainer; +import org.eclipse.edc.connector.dataplane.spi.pipeline.PipelineService; +import org.eclipse.edc.connector.policy.spi.PolicyDefinition; +import org.eclipse.edc.connector.policy.spi.store.PolicyDefinitionStore; +import org.eclipse.edc.policy.model.Policy; +import org.eclipse.edc.policy.model.PolicyType; +import org.eclipse.edc.runtime.metamodel.annotation.Inject; +import org.eclipse.edc.spi.asset.AssetIndex; +import org.eclipse.edc.spi.system.ServiceExtension; +import org.eclipse.edc.spi.system.ServiceExtensionContext; +import org.eclipse.edc.spi.types.domain.DataAddress; +import org.eclipse.edc.spi.types.domain.asset.Asset; +import org.fraunhofer.fit.extension.extractor.MetadataExtractor; + +import java.nio.file.Path; + +import static org.eclipse.edc.spi.query.Criterion.criterion; + +public class FileTransferExtension implements ServiceExtension { + + public static final String USE_POLICY = "use-eu"; + private static final String EDC_ASSET_PATH = "edc.samples.transfer.01.asset.path"; + private static final String EDC_ASSET_PID = "edc.samples.transfer.01.asset.pid"; + private static final String EDC_ASSET_LINK = "edc.samples.transfer.01.asset.link"; + private static final String EDC_ASSET_VERSION = "edc.samples.transfer.01.asset.version"; + @Inject + private ContractDefinitionStore contractStore; + @Inject + private AssetIndex assetIndex; + @Inject + private PipelineService pipelineService; + @Inject + private DataTransferExecutorServiceContainer executorContainer; + @Inject + private PolicyDefinitionStore policyStore; + @Inject + private MetadataExtractor metadataExtractor; + + @Override + public void initialize(ServiceExtensionContext context) { + var monitor = context.getMonitor(); + + var sourceFactory = new FileTransferDataSourceFactory(); + pipelineService.registerFactory(sourceFactory); + + var sinkFactory = new FileTransferDataSinkFactory(monitor, executorContainer.getExecutorService(), 5); + pipelineService.registerFactory(sinkFactory); + + var policy = createPolicy(); + policyStore.create(policy); + + registerDataEntries(context); + registerContractDefinition(policy.getUid()); + + context.getMonitor().info("File Transfer Extension initialized!"); + } + + private PolicyDefinition createPolicy() { + return PolicyDefinition.Builder.newInstance() + .id(USE_POLICY) + .policy(Policy.Builder.newInstance() + .type(PolicyType.SET) + .build()) + .build(); + } + + private void registerDataEntries(ServiceExtensionContext context) { + var assetPathSetting = context.getSetting(EDC_ASSET_PATH, "/tmp/provider/test-document.txt"); + var assetpid = context.getSetting(EDC_ASSET_PID, ""); + var assetDataLink = context.getSetting(EDC_ASSET_LINK, ""); + var assetVersion = context.getSetting(EDC_ASSET_VERSION, "v1"); + var assetPath = Path.of(assetPathSetting); + + var dataAddress = DataAddress.Builder.newInstance() + .property("type", "File") + .property("path", assetPath.getParent().toString()) + .property("filename", assetPath.getFileName().toString()) + .build(); + + var metadata = metadataExtractor.getMetadata(assetPath.toString(), assetpid, assetDataLink, assetVersion); + var assetId = "test-document"; + var asset = Asset.Builder.newInstance() + .id(assetId) + .contentType(metadata.remove("mimeType").toString()) + .name(metadata.remove("name").toString()) + .description(metadata.remove("description").toString()) + .version(metadata.remove("version").toString()) + .properties(metadata) + .build(); + + assetIndex.create(asset, dataAddress); + } + + private void registerContractDefinition(String uid) { + var contractDefinition = ContractDefinition.Builder.newInstance() + .id("1") + .accessPolicyId(uid) + .contractPolicyId(uid) + .assetsSelectorCriterion(criterion(Asset.PROPERTY_ID, "=", "test-document")) + .build(); + + contractStore.save(contractDefinition); + } +} diff --git a/sample/transfer/transfer-01-file-transfer/transfer-file-local/src/main/resources/META-INF/services/org.eclipse.edc.spi.system.ServiceExtension b/sample/transfer/transfer-01-file-transfer/transfer-file-local/src/main/resources/META-INF/services/org.eclipse.edc.spi.system.ServiceExtension new file mode 100644 index 0000000..5cc8c30 --- /dev/null +++ b/sample/transfer/transfer-01-file-transfer/transfer-file-local/src/main/resources/META-INF/services/org.eclipse.edc.spi.system.ServiceExtension @@ -0,0 +1 @@ +org.eclipse.edc.sample.extension.api.FileTransferExtension \ No newline at end of file -- GitLab