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