diff --git a/.github/dependabot.yml b/.github/dependabot.yml
new file mode 100644
index 0000000..a12ffeb
--- /dev/null
+++ b/.github/dependabot.yml
@@ -0,0 +1,17 @@
+version: 2
+updates:
+ - package-ecosystem: "maven"
+ directory: "/"
+ schedule:
+ interval: "weekly"
+ target-branch: "develop"
+ labels:
+ - "dependencies"
+
+ - package-ecosystem: "github-actions"
+ directory: "/"
+ schedule:
+ interval: "weekly"
+ target-branch: "develop"
+ labels:
+ - "dependencies"
diff --git a/.github/workflows/build.yml b/.github/workflows/build.yml
new file mode 100644
index 0000000..7e83880
--- /dev/null
+++ b/.github/workflows/build.yml
@@ -0,0 +1,149 @@
+name: "Build"
+
+on:
+ push:
+ pull_request:
+
+jobs:
+
+ test:
+ name: Maven Test & Package
+ runs-on: ubuntu-latest
+
+ env:
+ JAVA_DISTRIBUTION: temurin
+ JAVA_VERSION: 17
+
+ steps:
+ - name: Checkout repository
+ uses: actions/checkout@v3
+ with:
+ fetch-depth: 0
+
+ - name: Setup Java
+ uses: actions/setup-java@v3
+ with:
+ distribution: ${{ env.JAVA_DISTRIBUTION }}
+ java-version: ${{ env.JAVA_VERSION }}
+ cache: 'maven'
+
+ - name: Verify Maven and Java
+ run: |
+ mvn --version
+
+ - name: Run tests
+ run: |
+ mvn --quiet -U -B org.jacoco:jacoco-maven-plugin:prepare-agent test -Dspring.profiles.active=ci
+
+ - name: Build package
+ run: |
+ mvn --quiet -B -U --fail-fast -DskipTests package
+
+ docker:
+ name: Docker build
+ runs-on: ubuntu-latest
+ needs: test
+
+ env:
+ PUBLIC_IMAGE: fairdata/fairdatastation
+ PRIVATE_IMAGE: ${{ secrets.PRIVATE_REGISTRY_URL }}/fairdatastation
+ PRIVATE_REGISTRY_URL: ${{ secrets.PRIVATE_REGISTRY_URL }}
+ DOCKER_HUB_USERNAME: ${{ secrets.DOCKER_HUB_USERNAME }}
+
+ steps:
+ - name: Checkout repo
+ uses: actions/checkout@v3
+ with:
+ fetch-depth: 0
+
+ - name: Set up QEMU
+ uses: docker/setup-qemu-action@v2
+
+ - name: Set up Docker Buildx
+ id: buildx
+ uses: docker/setup-buildx-action@v2
+
+ - name: Check available platforms
+ run: echo ${{ steps.buildx.outputs.platforms }}
+
+ - name: Docker meta [test]
+ id: meta-test
+ uses: docker/metadata-action@v4
+ with:
+ images: |
+ ${{ env.PUBLIC_IMAGE }}
+ tags: |
+ type=sha
+ - name: Docker build+push [test]
+ uses: docker/build-push-action@v4
+ with:
+ context: .
+ file: ./Dockerfile
+ platforms: linux/amd64,linux/arm64
+ push: false
+ tags: ${{ steps.meta-test.outputs.tags }}
+ labels: ${{ steps.meta-test.outputs.labels }}
+
+ # PRIVATE: DOCKER REGISTRY
+ - name: Docker login [private]
+ if: github.event_name == 'push' && env.PRIVATE_REGISTRY_URL != ''
+ uses: docker/login-action@v2
+ with:
+ registry: ${{ secrets.PRIVATE_REGISTRY_URL }}
+ username: ${{ secrets.PRIVATE_REGISTRY_USERNAME }}
+ password: ${{ secrets.PRIVATE_REGISTRY_PASSWORD }}
+
+ - name: Docker meta [private]
+ id: meta-private
+ if: github.event_name == 'push' && env.PRIVATE_REGISTRY_URL != ''
+ uses: docker/metadata-action@v4
+ with:
+ images: |
+ ${{ env.PRIVATE_IMAGE }}
+ tags: |
+ type=ref,event=branch
+ type=semver,pattern={{version}}
+
+ - name: Docker build+push [private]
+ uses: docker/build-push-action@v4
+ if: github.event_name == 'push' && env.PRIVATE_REGISTRY_URL != '' && steps.meta-private.outputs.tags != ''
+ with:
+ context: .
+ file: ./Dockerfile
+ platforms: linux/amd64,linux/arm64
+ push: ${{ github.event_name != 'pull_request' }}
+ tags: ${{ steps.meta-private.outputs.tags }}
+ labels: ${{ steps.meta-private.outputs.labels }}
+
+ # PUBLIC: DOCKER HUB
+ - name: Docker login [public]
+ if: github.event_name == 'push' && env.DOCKER_HUB_USERNAME != ''
+ uses: docker/login-action@v2
+ with:
+ username: ${{ secrets.DOCKER_HUB_USERNAME }}
+ password: ${{ secrets.DOCKER_HUB_PASSWORD }}
+
+ - name: Docker meta [public]
+ id: meta-public
+ if: github.event_name == 'push' && env.DOCKER_HUB_USERNAME != ''
+ uses: docker/metadata-action@v4
+ with:
+ images: |
+ ${{ env.PUBLIC_IMAGE }}
+ tags: |
+ type=raw,value=develop,enable=${{ github.ref == format('refs/heads/{0}', 'develop') }}
+ type=raw,value=latest,enable=${{ github.ref == format('refs/heads/{0}', 'master') }}
+ type=semver,pattern={{version}}
+ type=semver,pattern={{major}}.{{minor}}
+ type=semver,pattern={{major}},enable=${{ !startsWith(github.ref, 'refs/tags/v0.') }}
+
+ - name: Docker build+push [public]
+ uses: docker/build-push-action@v4
+ if: github.event_name == 'push' && env.DOCKER_HUB_USERNAME != '' && steps.meta-public.outputs.tags != ''
+ with:
+ context: .
+ file: ./Dockerfile
+ platforms: linux/amd64,linux/arm64
+ push: ${{ github.event_name != 'pull_request' }}
+ tags: ${{ steps.meta-public.outputs.tags }}
+ labels: ${{ steps.meta-public.outputs.labels }}
diff --git a/.github/workflows/code-style.yml b/.github/workflows/code-style.yml
new file mode 100644
index 0000000..1c33495
--- /dev/null
+++ b/.github/workflows/code-style.yml
@@ -0,0 +1,76 @@
+name: "Code Style"
+
+on:
+ push:
+ pull_request:
+
+jobs:
+
+ checkstyle:
+ name: Checkstyle
+ runs-on: ubuntu-latest
+
+ steps:
+ - name: Checkout repository
+ uses: actions/checkout@v3
+
+ - name: Setup Java
+ uses: actions/setup-java@v3
+ with:
+ distribution: 'temurin'
+ java-version: '17'
+ cache: 'maven'
+
+ - name: Verify Maven and Java
+ run: |
+ mvn --version
+
+ - name: Run Checkstyle
+ run: |
+ mvn -B checkstyle:checkstyle
+
+ spotbugs:
+ name: SpotBugs
+ runs-on: ubuntu-latest
+
+ steps:
+ - name: Checkout repository
+ uses: actions/checkout@v3
+
+ - name: Setup Java
+ uses: actions/setup-java@v3
+ with:
+ distribution: 'temurin'
+ java-version: '17'
+ cache: 'maven'
+
+ - name: Verify Maven and Java
+ run: |
+ mvn --version
+
+ - name: Run SpotBugs
+ run: |
+ mvn -B spotbugs:check
+
+ license-head:
+ name: License Headers
+ runs-on: ubuntu-latest
+
+ steps:
+ - name: Checkout repository
+ uses: actions/checkout@v3
+
+ - name: Setup Java
+ uses: actions/setup-java@v3
+ with:
+ distribution: 'temurin'
+ java-version: '17'
+ cache: 'maven'
+
+ - name: Verify Maven and Java
+ run: |
+ mvn --version
+
+ - name: Check license
+ run: |
+ mvn -B license:check
diff --git a/.github/workflows/security.yml b/.github/workflows/security.yml
new file mode 100644
index 0000000..ad7e07a
--- /dev/null
+++ b/.github/workflows/security.yml
@@ -0,0 +1,93 @@
+name: "Security Audit"
+
+on:
+ push:
+ branches: [ develop, master ]
+ pull_request:
+ branches: [ develop ]
+ schedule:
+ - cron: '23 4 * * 1'
+
+jobs:
+ codeql:
+ name: CodeQL
+ runs-on: ubuntu-latest
+ permissions:
+ actions: read
+ contents: read
+ security-events: write
+
+ steps:
+ - name: Checkout repository
+ uses: actions/checkout@v3
+ with:
+ fetch-depth: 0
+
+ - name: Setup Java
+ uses: actions/setup-java@v2
+ with:
+ distribution: 'temurin'
+ java-version: '17'
+ cache: 'maven'
+
+ - name: Verify Maven and Java
+ run: |
+ mvn --version
+
+ - name: Initialize CodeQL
+ uses: github/codeql-action/init@v2
+ with:
+ languages: 'java'
+
+ - name: Build package
+ run: |
+ mvn --quiet -B -U --fail-fast -DskipTests package
+
+ - name: Perform CodeQL Analysis
+ uses: github/codeql-action/analyze@v2
+
+ snyk:
+ name: Snyk (Maven)
+ runs-on: ubuntu-latest
+ permissions:
+ actions: read
+ contents: read
+ security-events: write
+
+ steps:
+
+ - name: Checkout repository
+ uses: actions/checkout@v3
+
+ - name: Perform Snyk Check (Maven)
+ uses: snyk/actions/maven@master
+ env:
+ SNYK_TOKEN: ${{ secrets.SNYK_TOKEN }}
+ with:
+ args: --severity-threshold=critical
+
+ snyk-docker:
+ name: Snyk (Docker)
+ runs-on: ubuntu-latest
+ permissions:
+ actions: read
+ contents: read
+ security-events: write
+
+ steps:
+
+ - name: Checkout repository
+ uses: actions/checkout@v3
+
+ - name: Docker build
+ run: |
+ docker build -t fdp:snyk-test -f Dockerfile .
+
+ - name: Perform Snyk Check (Docker)
+ uses: snyk/actions/docker@master
+ continue-on-error: true
+ env:
+ SNYK_TOKEN: ${{ secrets.SNYK_TOKEN }}
+ with:
+ image: fdp:snyk-test
+ args: --severity-threshold=critical
diff --git a/.gitignore b/.gitignore
new file mode 100644
index 0000000..273ae66
--- /dev/null
+++ b/.gitignore
@@ -0,0 +1,36 @@
+HELP.md
+target/
+!.mvn/wrapper/maven-wrapper.jar
+!**/src/main/**/target/
+!**/src/test/**/target/
+
+### STS ###
+.apt_generated
+.classpath
+.factorypath
+.project
+.settings
+.springBeans
+.sts4-cache
+
+### IntelliJ IDEA ###
+.idea
+*.iws
+*.iml
+*.ipr
+
+### NetBeans ###
+/nbproject/private/
+/nbbuild/
+/dist/
+/nbdist/
+/.nb-gradle/
+build/
+!**/src/main/**/build/
+!**/src/test/**/build/
+
+### VS Code ###
+.vscode/
+
+### Custom ###
+logs/
diff --git a/CHANGELOG.md b/CHANGELOG.md
index 4b4bf84..c9a61fa 100644
--- a/CHANGELOG.md
+++ b/CHANGELOG.md
@@ -7,9 +7,13 @@ to [Semantic Versioning](https://semver.org/spec/v2.0.0.html).
## [Unreleased]
+
+## [0.1.0]
+
### Added
- Initiated FAIR Data Station project
[Unreleased]: /../../compare/master...develop
+[0.1.0]: /../../tree/v0.1.0
diff --git a/Dockerfile b/Dockerfile
new file mode 100644
index 0000000..b3614a9
--- /dev/null
+++ b/Dockerfile
@@ -0,0 +1,47 @@
+#
+# The MIT License
+# Copyright © 2022 FAIR Data Team
+#
+# Permission is hereby granted, free of charge, to any person obtaining a copy
+# of this software and associated documentation files (the "Software"), to deal
+# in the Software without restriction, including without limitation the rights
+# to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
+# copies of the Software, and to permit persons to whom the Software is
+# furnished to do so, subject to the following conditions:
+#
+# The above copyright notice and this permission notice shall be included in
+# all copies or substantial portions of the Software.
+#
+# THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
+# IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+# FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
+# AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
+# LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
+# OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
+# THE SOFTWARE.
+#
+
+FROM maven:3-eclipse-temurin-17 as builder
+
+WORKDIR /builder
+
+ADD . /builder
+
+RUN mvn --quiet -B -U --fail-fast -DskipTests package
+
+################################################################################
+# RUN STAGE
+FROM eclipse-temurin:17
+
+WORKDIR /app
+EXPOSE 8080
+
+ENV SPRING_PROFILE=production
+
+# Mount point for rolling log files
+RUN mkdir /app/logs
+
+COPY --from=builder /builder/target/app.jar /app/app.jar
+COPY --from=builder /builder/target/classes/application.yml /app/application.yml
+
+ENTRYPOINT java -jar app.jar --spring.profiles.active=$SPRING_PROFILE --spring.config.location=classpath:/application.yml,file:/app/application.yml
diff --git a/README.md b/README.md
index 87fd9f2..a9a18b1 100644
--- a/README.md
+++ b/README.md
@@ -4,11 +4,85 @@
## Usage
-*To be done*
+The recommended use is with Docker and Docker Compose and configuration via environment variables:
+
+```yml
+ fds:
+ image: fairdata/fairdatastation:latest
+ restart: unless-stopped
+ # ports:
+ # - 127.0.0.1:8080:8080
+ depends_on:
+ - postgres
+ # volumes:
+ # - ${PROJECT_ROOT}/application.fds.yml:/app/application.yml:ro
+ environment:
+ FDS_FDP_URL: http://fdp-client
+ FDS_POSTGRES_DB: ${POSTGRES_DB}
+ FDS_POSTGRES_USER: ${POSTGRES_USER}
+ FDS_POSTGRES_PASSWORD: ${POSTGRES_PASSWORD}
+ # FHIR endpoint (for FHIR Trains)
+ FDS_FHIR_BASE_URL: ${FHIR_API_BASE}
+ # Triple Store (for SPARQL Trains)
+ FDS_TRIPLE_STORE_TYPE: 4
+ FDS_TRIPLE_STORE_URL: ${GRAPHDB_URL}
+ FDS_TRIPLE_STORE_REPOSITORY: ${GRAPHDB_DATA_REPO}
+ FDS_TRIPLE_STORE_USERNAME: ${GRAPHDB_USERNAME}
+ FDS_TRIPLE_STORE_PASSWORD: ${GRAPHDB_PASSWORD}
+```
## Development
-*To be done*
+For development, we highly recommend using IDE and run Maven commands or the Spring Boot application from it.
+
+### Technology Stack
+
+- **Java** (JDK 17)
+- **PostgreSQL DB** (13 or higher)
+- **Maven** (3.6 or higher)
+- **Docker** (20.10 or higher) - *for building Docker image only*
+
+### Build & Run
+
+To run the application, a PostgreSQL DB is required to be running. To configure the standard connection
+suitable for development (`postgresql://localhost/fds`), simply instruct Spring Boot to use the `dev` profile:
+
+```bash
+$ mvn spring-boot:run -Dspring-boot.run.profiles=dev
+```
+
+Alternatively, create an `application.yml` file (or edit appropriate one) in the project root, and then run:
+
+```bash
+$ mvn spring-boot:run
+```
+
+### Run tests
+
+Run from the root of the project:
+
+```bash
+$ mvn test
+```
+
+### Package the application
+
+Run from the root of the project:
+
+```bash
+$ mvn package
+```
+
+### Create a Docker image
+
+Run from the root of the project (requires building `jar` file using `mvn package` as shown above):
+
+```bash
+$ docker build -t fairdatastation:local .
+```
+
+Note: Supplied [`Dockerfile`](Dockerfile) is multistage and as such does not require Java nor Maven to be
+installed directly.
## Contributing
@@ -18,3 +92,7 @@ and [Security Policy](SECURITY.md).
## License
This project is licensed under the MIT License - see the [LICENSE](LICENSE) file for more details.
+
+## Acknowledgments
+
+This code is based on work contucted across a number of projects, in particular C4yourself (funder: Health Holland—Top Sector Life Sciences and Health, grant number: LSHM 21044_C4YOUR- SELF) and Personal Genetic Locker (funder: Nederlandse Organisa- tie voor Wetenschappelijk Onderzoek, grant number: 628.011.022).
diff --git a/checkstyle.xml b/checkstyle.xml
new file mode 100644
index 0000000..3a216f3
--- /dev/null
+++ b/checkstyle.xml
@@ -0,0 +1,789 @@
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
diff --git a/pom.xml b/pom.xml
new file mode 100644
index 0000000..366f941
--- /dev/null
+++ b/pom.xml
@@ -0,0 +1,273 @@
+
+
+ 4.0.0
+
+ org.springframework.boot
+ spring-boot-starter-parent
+ 3.0.2
+
+
+
+ org.fairdatatrain
+ fairdatastation
+ 0.1.0
+
+ FAIRDataStation
+ FAIR Data Station
+ 2022
+
+
+ The MIT License
+ https://opensource.org/licenses/MIT
+
+
+
+
+
+ Marek Suchánek
+ https://github.com/MarekSuchanek
+
+
+
+
+ 17
+
+
+ 2022.0.0
+ 1.18.26
+ 2.0.2
+ 42.5.4
+ 4.2.3
+
+
+ 4.1
+ 3.2.1
+ 4.7.3.2
+ 5.0.0
+ 0.2.0
+
+
+ 8.6.3
+
+
+
+
+ org.springframework.boot
+ spring-boot-starter-webflux
+
+
+ org.springframework.boot
+ spring-boot-starter-logging
+
+
+
+
+ org.springframework.boot
+ spring-boot-starter-log4j2
+
+
+ org.springframework.boot
+ spring-boot-starter-validation
+
+
+ org.springframework.boot
+ spring-boot-starter-data-jpa
+
+
+ org.springframework.boot
+ spring-boot-starter-actuator
+
+
+ org.springdoc
+ springdoc-openapi-starter-webflux-ui
+ ${springdoc.version}
+
+
+
+ org.springframework.cloud
+ spring-cloud-starter-gateway
+
+
+
+ org.projectlombok
+ lombok
+ ${lombok.version}
+
+
+
+ org.eclipse.rdf4j
+ rdf4j-runtime
+ ${rdf4j.version}
+ pom
+
+
+ ch.qos.logback
+ logback-classic
+
+
+
+
+ org.eclipse.rdf4j
+ rdf4j-rio-api
+ ${rdf4j.version}
+
+
+
+ org.flywaydb
+ flyway-core
+
+
+
+ org.postgresql
+ postgresql
+ ${postgresql.version}
+ runtime
+
+
+
+ org.springframework.boot
+ spring-boot-starter-test
+ test
+
+
+
+
+
+ org.springframework.cloud
+ spring-cloud-dependencies
+ ${spring-cloud.version}
+ pom
+ import
+
+
+
+
+
+ app
+
+
+ org.springframework.boot
+ spring-boot-maven-plugin
+
+
+
+ repackage
+
+
+ spring-boot
+ org.fairdatatrain.fairdatastation.Application
+
+
+
+ build-info
+
+ build-info
+
+
+
+
+
+ com.mycila
+ license-maven-plugin
+ ${plugin.license.version}
+
+
+ FAIR Data Team
+
+
+
+ com/mycila/maven/plugin/license/templates/MIT.txt
+
+ **/*.java
+ Dockerfile
+
+
+
+
+ JAVADOC_STYLE
+
+
+
+
+ process-sources
+
+ format
+
+
+
+
+
+ org.apache.maven.plugins
+ maven-checkstyle-plugin
+ ${plugin.checkstyle.version}
+
+ true
+ true
+ checkstyle.xml
+
+
+
+ com.puppycrawl.tools
+ checkstyle
+ 10.8.0
+
+
+ io.spring.javaformat
+ spring-javaformat-checkstyle
+ 0.0.38
+
+
+
+
+ com.github.spotbugs
+ spotbugs-maven-plugin
+ ${plugin.spotbugs.version}
+
+
+ io.github.git-commit-id
+ git-commit-id-maven-plugin
+ ${plugin.git_commit_id.version}
+
+
+ get-the-git-infos
+
+ revision
+
+ initialize
+
+
+
+ false
+ true
+ ${project.build.outputDirectory}/META-INF/git.properties
+ full
+
+
+
+ com.github.kburger
+ rdf4j-generator-maven-plugin
+ ${plugin.rdf4j_generator.version}
+
+
+
+ generate
+
+
+
+
+ nl.dtls.fairdatapoint.vocabulary
+ false
+
+
+ https://raw.githubusercontent.com/FAIRDataTeam/FDT-O/778dcc29d54608fc942eb6b3260e9667a6227edc/fdt-ontology.owl
+ https://w3id.org/fdt/fdt-o#
+ fdt
+
+
+
+
+
+
+
+
diff --git a/src/main/java/org/fairdatatrain/fairdatastation/Application.java b/src/main/java/org/fairdatatrain/fairdatastation/Application.java
new file mode 100644
index 0000000..419a6e8
--- /dev/null
+++ b/src/main/java/org/fairdatatrain/fairdatastation/Application.java
@@ -0,0 +1,43 @@
+/**
+ * The MIT License
+ * Copyright © 2022 FAIR Data Team
+ *
+ * Permission is hereby granted, free of charge, to any person obtaining a copy
+ * of this software and associated documentation files (the "Software"), to deal
+ * in the Software without restriction, including without limitation the rights
+ * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
+ * copies of the Software, and to permit persons to whom the Software is
+ * furnished to do so, subject to the following conditions:
+ *
+ * The above copyright notice and this permission notice shall be included in
+ * all copies or substantial portions of the Software.
+ *
+ * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
+ * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+ * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
+ * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
+ * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
+ * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
+ * THE SOFTWARE.
+ */
+package org.fairdatatrain.fairdatastation;
+
+import org.springframework.boot.SpringApplication;
+import org.springframework.boot.autoconfigure.SpringBootApplication;
+import org.springframework.boot.context.properties.ConfigurationPropertiesScan;
+import org.springframework.context.annotation.ComponentScan;
+import org.springframework.scheduling.annotation.EnableAsync;
+import org.springframework.scheduling.annotation.EnableScheduling;
+
+@EnableAsync
+@EnableScheduling
+@SpringBootApplication
+@ComponentScan(basePackages = "org.fairdatatrain.fairdatastation.*")
+@ConfigurationPropertiesScan("org.fairdatatrain.fairdatastation.config.*")
+public class Application {
+
+ public static void main(String[] args) {
+ SpringApplication.run(Application.class, args);
+ }
+
+}
diff --git a/src/main/java/org/fairdatatrain/fairdatastation/api/controller/RootController.java b/src/main/java/org/fairdatatrain/fairdatastation/api/controller/RootController.java
new file mode 100644
index 0000000..61e273c
--- /dev/null
+++ b/src/main/java/org/fairdatatrain/fairdatastation/api/controller/RootController.java
@@ -0,0 +1,48 @@
+/**
+ * The MIT License
+ * Copyright © 2022 FAIR Data Team
+ *
+ * Permission is hereby granted, free of charge, to any person obtaining a copy
+ * of this software and associated documentation files (the "Software"), to deal
+ * in the Software without restriction, including without limitation the rights
+ * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
+ * copies of the Software, and to permit persons to whom the Software is
+ * furnished to do so, subject to the following conditions:
+ *
+ * The above copyright notice and this permission notice shall be included in
+ * all copies or substantial portions of the Software.
+ *
+ * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
+ * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+ * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
+ * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
+ * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
+ * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
+ * THE SOFTWARE.
+ */
+package org.fairdatatrain.fairdatastation.api.controller;
+
+import io.swagger.v3.oas.annotations.tags.Tag;
+import lombok.RequiredArgsConstructor;
+import org.fairdatatrain.fairdatastation.api.dto.root.RootInfoDTO;
+import org.springframework.http.MediaType;
+import org.springframework.http.server.reactive.ServerHttpRequest;
+import org.springframework.web.bind.annotation.GetMapping;
+import org.springframework.web.bind.annotation.RequestMapping;
+import org.springframework.web.bind.annotation.RestController;
+
+@Tag(name = "Train Handling")
+@RestController
+@RequestMapping("")
+@RequiredArgsConstructor
+public class RootController {
+
+ @GetMapping(path = "", produces = MediaType.APPLICATION_JSON_VALUE)
+ public RootInfoDTO getRootInfo(ServerHttpRequest serverHttpRequest) {
+ final String baseUrl = serverHttpRequest.getURI().toString();
+ return RootInfoDTO.builder()
+ .fdpEndpoint(baseUrl + "fdp")
+ .trainEndpoint(baseUrl + "trains")
+ .build();
+ }
+}
diff --git a/src/main/java/org/fairdatatrain/fairdatastation/api/controller/event/JobArtifactController.java b/src/main/java/org/fairdatatrain/fairdatastation/api/controller/event/JobArtifactController.java
new file mode 100644
index 0000000..24be459
--- /dev/null
+++ b/src/main/java/org/fairdatatrain/fairdatastation/api/controller/event/JobArtifactController.java
@@ -0,0 +1,84 @@
+/**
+ * The MIT License
+ * Copyright © 2022 FAIR Data Team
+ *
+ * Permission is hereby granted, free of charge, to any person obtaining a copy
+ * of this software and associated documentation files (the "Software"), to deal
+ * in the Software without restriction, including without limitation the rights
+ * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
+ * copies of the Software, and to permit persons to whom the Software is
+ * furnished to do so, subject to the following conditions:
+ *
+ * The above copyright notice and this permission notice shall be included in
+ * all copies or substantial portions of the Software.
+ *
+ * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
+ * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+ * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
+ * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
+ * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
+ * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
+ * THE SOFTWARE.
+ */
+package org.fairdatatrain.fairdatastation.api.controller.event;
+
+import io.swagger.v3.oas.annotations.tags.Tag;
+import lombok.RequiredArgsConstructor;
+import org.fairdatatrain.fairdatastation.api.dto.event.job.artifact.JobArtifactDTO;
+import org.fairdatatrain.fairdatastation.data.model.event.JobArtifact;
+import org.fairdatatrain.fairdatastation.exception.NotFoundException;
+import org.fairdatatrain.fairdatastation.service.event.job.artifact.JobArtifactService;
+import org.springframework.core.io.ByteArrayResource;
+import org.springframework.core.io.Resource;
+import org.springframework.http.HttpHeaders;
+import org.springframework.http.MediaType;
+import org.springframework.http.ResponseEntity;
+import org.springframework.web.bind.annotation.GetMapping;
+import org.springframework.web.bind.annotation.PathVariable;
+import org.springframework.web.bind.annotation.RequestMapping;
+import org.springframework.web.bind.annotation.RestController;
+
+import java.util.List;
+import java.util.UUID;
+
+import static java.lang.String.format;
+
+@Tag(name = "Jobs")
+@RestController
+@RequestMapping("/jobs")
+@RequiredArgsConstructor
+public class JobArtifactController {
+
+ private final JobArtifactService jobArtifactService;
+
+ @GetMapping(
+ path = "/{jobUuid}/artifacts",
+ produces = MediaType.APPLICATION_JSON_VALUE
+ )
+ public List getJobArtifacts(
+ @PathVariable UUID jobUuid
+ ) throws NotFoundException {
+ return jobArtifactService.getArtifactsForJob(jobUuid);
+ }
+
+ @GetMapping(
+ path = "/{jobUuid}/artifacts/{artifactUuid}/download",
+ produces = MediaType.APPLICATION_JSON_VALUE
+ )
+ public ResponseEntity getJobArtifactData(
+ @PathVariable UUID jobUuid, @PathVariable UUID artifactUuid
+ ) throws NotFoundException {
+ final JobArtifact artifact = jobArtifactService.getByIdOrThrow(jobUuid, artifactUuid);
+ final byte[] data = jobArtifactService.getArtifactData(artifact);
+ final ByteArrayResource resource = new ByteArrayResource(data);
+ return ResponseEntity
+ .ok()
+ .contentLength(artifact.getBytesize())
+ .contentType(MediaType.parseMediaType(artifact.getContentType()))
+ .header(
+ HttpHeaders.CONTENT_DISPOSITION,
+ format("attachment;filename=%s", artifact.getFilename())
+ )
+ .body(resource);
+ }
+}
diff --git a/src/main/java/org/fairdatatrain/fairdatastation/api/controller/event/JobController.java b/src/main/java/org/fairdatatrain/fairdatastation/api/controller/event/JobController.java
new file mode 100644
index 0000000..2ffbef1
--- /dev/null
+++ b/src/main/java/org/fairdatatrain/fairdatastation/api/controller/event/JobController.java
@@ -0,0 +1,63 @@
+/**
+ * The MIT License
+ * Copyright © 2022 FAIR Data Team
+ *
+ * Permission is hereby granted, free of charge, to any person obtaining a copy
+ * of this software and associated documentation files (the "Software"), to deal
+ * in the Software without restriction, including without limitation the rights
+ * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
+ * copies of the Software, and to permit persons to whom the Software is
+ * furnished to do so, subject to the following conditions:
+ *
+ * The above copyright notice and this permission notice shall be included in
+ * all copies or substantial portions of the Software.
+ *
+ * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
+ * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+ * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
+ * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
+ * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
+ * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
+ * THE SOFTWARE.
+ */
+package org.fairdatatrain.fairdatastation.api.controller.event;
+
+import io.swagger.v3.oas.annotations.tags.Tag;
+import lombok.RequiredArgsConstructor;
+import org.fairdatatrain.fairdatastation.api.dto.event.job.JobDTO;
+import org.fairdatatrain.fairdatastation.api.dto.event.job.JobSimpleDTO;
+import org.fairdatatrain.fairdatastation.exception.NotFoundException;
+import org.fairdatatrain.fairdatastation.service.event.job.JobService;
+import org.springframework.data.domain.Page;
+import org.springframework.data.domain.Pageable;
+import org.springframework.http.MediaType;
+import org.springframework.web.bind.annotation.GetMapping;
+import org.springframework.web.bind.annotation.PathVariable;
+import org.springframework.web.bind.annotation.RequestMapping;
+import org.springframework.web.bind.annotation.RestController;
+
+import java.util.UUID;
+
+@Tag(name = "Jobs")
+@RestController
+@RequestMapping("/jobs")
+@RequiredArgsConstructor
+public class JobController {
+
+ private final JobService jobService;
+
+ @GetMapping(path = "", produces = MediaType.APPLICATION_JSON_VALUE)
+ public Page getJobs(Pageable pageable) {
+ return jobService.getJobs(pageable);
+ }
+
+ @GetMapping(
+ path = "/{jobUuid}",
+ produces = MediaType.APPLICATION_JSON_VALUE
+ )
+ public JobDTO getJob(
+ @PathVariable UUID jobUuid
+ ) throws NotFoundException {
+ return jobService.getJob(jobUuid);
+ }
+}
diff --git a/src/main/java/org/fairdatatrain/fairdatastation/api/controller/event/JobEventController.java b/src/main/java/org/fairdatatrain/fairdatastation/api/controller/event/JobEventController.java
new file mode 100644
index 0000000..05cb4bf
--- /dev/null
+++ b/src/main/java/org/fairdatatrain/fairdatastation/api/controller/event/JobEventController.java
@@ -0,0 +1,56 @@
+/**
+ * The MIT License
+ * Copyright © 2022 FAIR Data Team
+ *
+ * Permission is hereby granted, free of charge, to any person obtaining a copy
+ * of this software and associated documentation files (the "Software"), to deal
+ * in the Software without restriction, including without limitation the rights
+ * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
+ * copies of the Software, and to permit persons to whom the Software is
+ * furnished to do so, subject to the following conditions:
+ *
+ * The above copyright notice and this permission notice shall be included in
+ * all copies or substantial portions of the Software.
+ *
+ * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
+ * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+ * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
+ * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
+ * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
+ * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
+ * THE SOFTWARE.
+ */
+package org.fairdatatrain.fairdatastation.api.controller.event;
+
+import io.swagger.v3.oas.annotations.tags.Tag;
+import lombok.RequiredArgsConstructor;
+import org.fairdatatrain.fairdatastation.api.dto.event.job.event.JobEventDTO;
+import org.fairdatatrain.fairdatastation.exception.NotFoundException;
+import org.fairdatatrain.fairdatastation.service.event.job.event.JobEventService;
+import org.springframework.http.MediaType;
+import org.springframework.web.bind.annotation.GetMapping;
+import org.springframework.web.bind.annotation.PathVariable;
+import org.springframework.web.bind.annotation.RequestMapping;
+import org.springframework.web.bind.annotation.RestController;
+
+import java.util.List;
+import java.util.UUID;
+
+@Tag(name = "Jobs")
+@RestController
+@RequestMapping("/jobs")
+@RequiredArgsConstructor
+public class JobEventController {
+
+ private final JobEventService jobEventService;
+
+ @GetMapping(
+ path = "/{jobUuid}/events",
+ produces = MediaType.APPLICATION_JSON_VALUE
+ )
+ public List getJobEvents(
+ @PathVariable UUID jobUuid
+ ) throws NotFoundException {
+ return jobEventService.getEventsForJob(jobUuid);
+ }
+}
diff --git a/src/main/java/org/fairdatatrain/fairdatastation/api/controller/event/TrainController.java b/src/main/java/org/fairdatatrain/fairdatastation/api/controller/event/TrainController.java
new file mode 100644
index 0000000..4e2369f
--- /dev/null
+++ b/src/main/java/org/fairdatatrain/fairdatastation/api/controller/event/TrainController.java
@@ -0,0 +1,56 @@
+/**
+ * The MIT License
+ * Copyright © 2022 FAIR Data Team
+ *
+ * Permission is hereby granted, free of charge, to any person obtaining a copy
+ * of this software and associated documentation files (the "Software"), to deal
+ * in the Software without restriction, including without limitation the rights
+ * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
+ * copies of the Software, and to permit persons to whom the Software is
+ * furnished to do so, subject to the following conditions:
+ *
+ * The above copyright notice and this permission notice shall be included in
+ * all copies or substantial portions of the Software.
+ *
+ * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
+ * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+ * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
+ * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
+ * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
+ * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
+ * THE SOFTWARE.
+ */
+package org.fairdatatrain.fairdatastation.api.controller.event;
+
+import io.swagger.v3.oas.annotations.tags.Tag;
+import jakarta.validation.Valid;
+import lombok.RequiredArgsConstructor;
+import org.fairdatatrain.fairdatastation.api.dto.event.train.TrainDispatchPayloadDTO;
+import org.fairdatatrain.fairdatastation.api.dto.event.train.TrainDispatchResponseDTO;
+import org.fairdatatrain.fairdatastation.service.event.TrainEventService;
+import org.springframework.http.MediaType;
+import org.springframework.web.bind.annotation.PostMapping;
+import org.springframework.web.bind.annotation.RequestBody;
+import org.springframework.web.bind.annotation.RequestMapping;
+import org.springframework.web.bind.annotation.RestController;
+
+@Tag(name = "Trains")
+@RestController
+@RequestMapping("/trains")
+@RequiredArgsConstructor
+public class TrainController {
+
+ private final TrainEventService trainEventService;
+
+ @PostMapping(
+ path = "",
+ consumes = MediaType.APPLICATION_JSON_VALUE,
+ produces = MediaType.APPLICATION_JSON_VALUE
+ )
+ public TrainDispatchResponseDTO acceptTrain(
+ @Valid @RequestBody TrainDispatchPayloadDTO reqDto
+ ) {
+ // TODO: check/store origin? filtering?
+ return trainEventService.acceptTrain(reqDto);
+ }
+}
diff --git a/src/main/java/org/fairdatatrain/fairdatastation/api/dto/event/job/JobDTO.java b/src/main/java/org/fairdatatrain/fairdatastation/api/dto/event/job/JobDTO.java
new file mode 100644
index 0000000..8ab8d2a
--- /dev/null
+++ b/src/main/java/org/fairdatatrain/fairdatastation/api/dto/event/job/JobDTO.java
@@ -0,0 +1,60 @@
+/**
+ * The MIT License
+ * Copyright © 2022 FAIR Data Team
+ *
+ * Permission is hereby granted, free of charge, to any person obtaining a copy
+ * of this software and associated documentation files (the "Software"), to deal
+ * in the Software without restriction, including without limitation the rights
+ * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
+ * copies of the Software, and to permit persons to whom the Software is
+ * furnished to do so, subject to the following conditions:
+ *
+ * The above copyright notice and this permission notice shall be included in
+ * all copies or substantial portions of the Software.
+ *
+ * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
+ * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+ * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
+ * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
+ * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
+ * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
+ * THE SOFTWARE.
+ */
+package org.fairdatatrain.fairdatastation.api.dto.event.job;
+
+import lombok.*;
+import org.fairdatatrain.fairdatastation.api.dto.event.job.artifact.JobArtifactDTO;
+import org.fairdatatrain.fairdatastation.api.dto.event.job.event.JobEventDTO;
+import org.fairdatatrain.fairdatastation.data.model.enums.JobStatus;
+
+import java.time.Instant;
+import java.util.List;
+import java.util.UUID;
+
+@AllArgsConstructor
+@NoArgsConstructor
+@Getter
+@Setter
+@Builder(toBuilder = true)
+public class JobDTO {
+
+ private UUID uuid;
+
+ private String remoteId;
+
+ private JobStatus status;
+
+ private Instant startedAt;
+
+ private Instant finishedAt;
+
+ private List events;
+
+ private List artifacts;
+
+ private Instant createdAt;
+
+ private Instant updatedAt;
+
+ private Long version;
+}
diff --git a/src/main/java/org/fairdatatrain/fairdatastation/api/dto/event/job/JobSimpleDTO.java b/src/main/java/org/fairdatatrain/fairdatastation/api/dto/event/job/JobSimpleDTO.java
new file mode 100644
index 0000000..42b7efa
--- /dev/null
+++ b/src/main/java/org/fairdatatrain/fairdatastation/api/dto/event/job/JobSimpleDTO.java
@@ -0,0 +1,57 @@
+/**
+ * The MIT License
+ * Copyright © 2022 FAIR Data Team
+ *
+ * Permission is hereby granted, free of charge, to any person obtaining a copy
+ * of this software and associated documentation files (the "Software"), to deal
+ * in the Software without restriction, including without limitation the rights
+ * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
+ * copies of the Software, and to permit persons to whom the Software is
+ * furnished to do so, subject to the following conditions:
+ *
+ * The above copyright notice and this permission notice shall be included in
+ * all copies or substantial portions of the Software.
+ *
+ * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
+ * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+ * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
+ * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
+ * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
+ * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
+ * THE SOFTWARE.
+ */
+package org.fairdatatrain.fairdatastation.api.dto.event.job;
+
+import lombok.*;
+import org.fairdatatrain.fairdatastation.api.dto.event.job.artifact.JobArtifactDTO;
+import org.fairdatatrain.fairdatastation.data.model.enums.JobStatus;
+
+import java.time.Instant;
+import java.util.List;
+import java.util.UUID;
+
+@AllArgsConstructor
+@NoArgsConstructor
+@Getter
+@Setter
+@Builder(toBuilder = true)
+public class JobSimpleDTO {
+
+ private UUID uuid;
+
+ private String remoteId;
+
+ private JobStatus status;
+
+ private Instant startedAt;
+
+ private Instant finishedAt;
+
+ private List artifacts;
+
+ private Instant createdAt;
+
+ private Instant updatedAt;
+
+ private Long version;
+}
diff --git a/src/main/java/org/fairdatatrain/fairdatastation/api/dto/event/job/artifact/JobArtifactDTO.java b/src/main/java/org/fairdatatrain/fairdatastation/api/dto/event/job/artifact/JobArtifactDTO.java
new file mode 100644
index 0000000..e973d44
--- /dev/null
+++ b/src/main/java/org/fairdatatrain/fairdatastation/api/dto/event/job/artifact/JobArtifactDTO.java
@@ -0,0 +1,54 @@
+/**
+ * The MIT License
+ * Copyright © 2022 FAIR Data Team
+ *
+ * Permission is hereby granted, free of charge, to any person obtaining a copy
+ * of this software and associated documentation files (the "Software"), to deal
+ * in the Software without restriction, including without limitation the rights
+ * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
+ * copies of the Software, and to permit persons to whom the Software is
+ * furnished to do so, subject to the following conditions:
+ *
+ * The above copyright notice and this permission notice shall be included in
+ * all copies or substantial portions of the Software.
+ *
+ * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
+ * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+ * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
+ * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
+ * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
+ * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
+ * THE SOFTWARE.
+ */
+package org.fairdatatrain.fairdatastation.api.dto.event.job.artifact;
+
+import lombok.*;
+
+import java.time.Instant;
+import java.util.UUID;
+
+@AllArgsConstructor
+@NoArgsConstructor
+@Getter
+@Setter
+@Builder(toBuilder = true)
+public class JobArtifactDTO {
+
+ private UUID uuid;
+
+ private String displayName;
+
+ private String filename;
+
+ private Long bytesize;
+
+ private String contentType;
+
+ private String hash;
+
+ private Instant occurredAt;
+
+ private Instant createdAt;
+
+ private Instant updatedAt;
+}
diff --git a/src/main/java/org/fairdatatrain/fairdatastation/api/dto/event/job/artifact/JobArtifactDispatchDTO.java b/src/main/java/org/fairdatatrain/fairdatastation/api/dto/event/job/artifact/JobArtifactDispatchDTO.java
new file mode 100644
index 0000000..324a7e7
--- /dev/null
+++ b/src/main/java/org/fairdatatrain/fairdatastation/api/dto/event/job/artifact/JobArtifactDispatchDTO.java
@@ -0,0 +1,64 @@
+/**
+ * The MIT License
+ * Copyright © 2022 FAIR Data Team
+ *
+ * Permission is hereby granted, free of charge, to any person obtaining a copy
+ * of this software and associated documentation files (the "Software"), to deal
+ * in the Software without restriction, including without limitation the rights
+ * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
+ * copies of the Software, and to permit persons to whom the Software is
+ * furnished to do so, subject to the following conditions:
+ *
+ * The above copyright notice and this permission notice shall be included in
+ * all copies or substantial portions of the Software.
+ *
+ * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
+ * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+ * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
+ * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
+ * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
+ * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
+ * THE SOFTWARE.
+ */
+package org.fairdatatrain.fairdatastation.api.dto.event.job.artifact;
+
+import jakarta.validation.constraints.NotNull;
+import lombok.*;
+
+import java.time.Instant;
+
+@AllArgsConstructor
+@NoArgsConstructor
+@Getter
+@Setter
+@Builder
+public class JobArtifactDispatchDTO {
+ // Same as handler's JobArtifactCreateDTO
+
+ @NotNull
+ private String displayName;
+
+ @NotNull
+ private String filename;
+
+ @NotNull
+ private String hash;
+
+ @NotNull
+ private Long bytesize;
+
+ @NotNull
+ private String contentType;
+
+ @NotNull
+ private Instant occurredAt;
+
+ @NotNull
+ private String remoteId;
+
+ @NotNull
+ private String secret;
+
+ @NotNull
+ private String base64data;
+}
diff --git a/src/main/java/org/fairdatatrain/fairdatastation/api/dto/event/job/event/JobEventDTO.java b/src/main/java/org/fairdatatrain/fairdatastation/api/dto/event/job/event/JobEventDTO.java
new file mode 100644
index 0000000..9ce80db
--- /dev/null
+++ b/src/main/java/org/fairdatatrain/fairdatastation/api/dto/event/job/event/JobEventDTO.java
@@ -0,0 +1,49 @@
+/**
+ * The MIT License
+ * Copyright © 2022 FAIR Data Team
+ *
+ * Permission is hereby granted, free of charge, to any person obtaining a copy
+ * of this software and associated documentation files (the "Software"), to deal
+ * in the Software without restriction, including without limitation the rights
+ * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
+ * copies of the Software, and to permit persons to whom the Software is
+ * furnished to do so, subject to the following conditions:
+ *
+ * The above copyright notice and this permission notice shall be included in
+ * all copies or substantial portions of the Software.
+ *
+ * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
+ * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+ * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
+ * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
+ * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
+ * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
+ * THE SOFTWARE.
+ */
+package org.fairdatatrain.fairdatastation.api.dto.event.job.event;
+
+import lombok.*;
+import org.fairdatatrain.fairdatastation.data.model.enums.JobStatus;
+
+import java.time.Instant;
+import java.util.UUID;
+
+@AllArgsConstructor
+@NoArgsConstructor
+@Getter
+@Setter
+@Builder(toBuilder = true)
+public class JobEventDTO {
+
+ private UUID uuid;
+
+ private JobStatus resultStatus;
+
+ private String message;
+
+ private Instant occurredAt;
+
+ private Instant createdAt;
+
+ private Instant updatedAt;
+}
diff --git a/src/main/java/org/fairdatatrain/fairdatastation/api/dto/event/job/event/JobEventDispatchDTO.java b/src/main/java/org/fairdatatrain/fairdatastation/api/dto/event/job/event/JobEventDispatchDTO.java
new file mode 100644
index 0000000..226ad82
--- /dev/null
+++ b/src/main/java/org/fairdatatrain/fairdatastation/api/dto/event/job/event/JobEventDispatchDTO.java
@@ -0,0 +1,52 @@
+/**
+ * The MIT License
+ * Copyright © 2022 FAIR Data Team
+ *
+ * Permission is hereby granted, free of charge, to any person obtaining a copy
+ * of this software and associated documentation files (the "Software"), to deal
+ * in the Software without restriction, including without limitation the rights
+ * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
+ * copies of the Software, and to permit persons to whom the Software is
+ * furnished to do so, subject to the following conditions:
+ *
+ * The above copyright notice and this permission notice shall be included in
+ * all copies or substantial portions of the Software.
+ *
+ * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
+ * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+ * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
+ * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
+ * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
+ * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
+ * THE SOFTWARE.
+ */
+package org.fairdatatrain.fairdatastation.api.dto.event.job.event;
+
+import jakarta.validation.constraints.NotNull;
+import lombok.*;
+import org.fairdatatrain.fairdatastation.data.model.enums.JobStatus;
+
+import java.time.Instant;
+
+@AllArgsConstructor
+@NoArgsConstructor
+@Getter
+@Setter
+@Builder
+public class JobEventDispatchDTO {
+ // Same as handler's JobArtifactCreateDTO
+
+ private JobStatus resultStatus;
+
+ @NotNull
+ private String message;
+
+ @NotNull
+ private Instant occurredAt;
+
+ @NotNull
+ private String remoteId;
+
+ @NotNull
+ private String secret;
+}
diff --git a/src/main/java/org/fairdatatrain/fairdatastation/api/dto/event/train/TrainDispatchPayloadDTO.java b/src/main/java/org/fairdatatrain/fairdatastation/api/dto/event/train/TrainDispatchPayloadDTO.java
new file mode 100644
index 0000000..06f43ea
--- /dev/null
+++ b/src/main/java/org/fairdatatrain/fairdatastation/api/dto/event/train/TrainDispatchPayloadDTO.java
@@ -0,0 +1,55 @@
+/**
+ * The MIT License
+ * Copyright © 2022 FAIR Data Team
+ *
+ * Permission is hereby granted, free of charge, to any person obtaining a copy
+ * of this software and associated documentation files (the "Software"), to deal
+ * in the Software without restriction, including without limitation the rights
+ * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
+ * copies of the Software, and to permit persons to whom the Software is
+ * furnished to do so, subject to the following conditions:
+ *
+ * The above copyright notice and this permission notice shall be included in
+ * all copies or substantial portions of the Software.
+ *
+ * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
+ * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+ * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
+ * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
+ * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
+ * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
+ * THE SOFTWARE.
+ */
+package org.fairdatatrain.fairdatastation.api.dto.event.train;
+
+import jakarta.validation.constraints.NotNull;
+import lombok.*;
+import org.fairdatatrain.fairdatastation.api.validator.ValidIri;
+
+@AllArgsConstructor
+@NoArgsConstructor
+@Getter
+@Setter
+@Builder(toBuilder = true)
+public class TrainDispatchPayloadDTO {
+
+ // TODO: rename "jobUuid"
+ @NotNull
+ private String jobUuid;
+
+ // TODO: minimal requirements for secret?
+ @NotNull
+ private String secret;
+
+ @NotNull
+ @ValidIri
+ private String callbackEventLocation;
+
+ @NotNull
+ @ValidIri
+ private String callbackArtifactLocation;
+
+ @NotNull
+ @ValidIri
+ private String trainUri;
+}
diff --git a/src/main/java/org/fairdatatrain/fairdatastation/api/dto/event/train/TrainDispatchResponseDTO.java b/src/main/java/org/fairdatatrain/fairdatastation/api/dto/event/train/TrainDispatchResponseDTO.java
new file mode 100644
index 0000000..f61f924
--- /dev/null
+++ b/src/main/java/org/fairdatatrain/fairdatastation/api/dto/event/train/TrainDispatchResponseDTO.java
@@ -0,0 +1,45 @@
+/**
+ * The MIT License
+ * Copyright © 2022 FAIR Data Team
+ *
+ * Permission is hereby granted, free of charge, to any person obtaining a copy
+ * of this software and associated documentation files (the "Software"), to deal
+ * in the Software without restriction, including without limitation the rights
+ * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
+ * copies of the Software, and to permit persons to whom the Software is
+ * furnished to do so, subject to the following conditions:
+ *
+ * The above copyright notice and this permission notice shall be included in
+ * all copies or substantial portions of the Software.
+ *
+ * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
+ * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+ * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
+ * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
+ * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
+ * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
+ * THE SOFTWARE.
+ */
+package org.fairdatatrain.fairdatastation.api.dto.event.train;
+
+import jakarta.validation.constraints.NotNull;
+import lombok.*;
+import org.fairdatatrain.fairdatastation.data.model.enums.JobStatus;
+
+@AllArgsConstructor
+@NoArgsConstructor
+@Getter
+@Setter
+@Builder(toBuilder = true)
+public class TrainDispatchResponseDTO {
+
+ @NotNull
+ private String id;
+
+ @NotNull
+ private String message;
+
+ @NotNull
+ private JobStatus status;
+
+}
diff --git a/src/main/java/org/fairdatatrain/fairdatastation/api/dto/root/RootInfoDTO.java b/src/main/java/org/fairdatatrain/fairdatastation/api/dto/root/RootInfoDTO.java
new file mode 100644
index 0000000..9893a37
--- /dev/null
+++ b/src/main/java/org/fairdatatrain/fairdatastation/api/dto/root/RootInfoDTO.java
@@ -0,0 +1,37 @@
+/**
+ * The MIT License
+ * Copyright © 2022 FAIR Data Team
+ *
+ * Permission is hereby granted, free of charge, to any person obtaining a copy
+ * of this software and associated documentation files (the "Software"), to deal
+ * in the Software without restriction, including without limitation the rights
+ * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
+ * copies of the Software, and to permit persons to whom the Software is
+ * furnished to do so, subject to the following conditions:
+ *
+ * The above copyright notice and this permission notice shall be included in
+ * all copies or substantial portions of the Software.
+ *
+ * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
+ * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+ * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
+ * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
+ * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
+ * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
+ * THE SOFTWARE.
+ */
+package org.fairdatatrain.fairdatastation.api.dto.root;
+
+import lombok.*;
+
+@NoArgsConstructor
+@AllArgsConstructor
+@Getter
+@Setter
+@Builder
+public class RootInfoDTO {
+
+ private String fdpEndpoint;
+
+ private String trainEndpoint;
+}
diff --git a/src/main/java/org/fairdatatrain/fairdatastation/api/filter/CorsFilter.java b/src/main/java/org/fairdatatrain/fairdatastation/api/filter/CorsFilter.java
new file mode 100644
index 0000000..7ae828c
--- /dev/null
+++ b/src/main/java/org/fairdatatrain/fairdatastation/api/filter/CorsFilter.java
@@ -0,0 +1,74 @@
+/**
+ * The MIT License
+ * Copyright © 2022 FAIR Data Team
+ *
+ * Permission is hereby granted, free of charge, to any person obtaining a copy
+ * of this software and associated documentation files (the "Software"), to deal
+ * in the Software without restriction, including without limitation the rights
+ * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
+ * copies of the Software, and to permit persons to whom the Software is
+ * furnished to do so, subject to the following conditions:
+ *
+ * The above copyright notice and this permission notice shall be included in
+ * all copies or substantial portions of the Software.
+ *
+ * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
+ * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+ * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
+ * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
+ * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
+ * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
+ * THE SOFTWARE.
+ */
+package org.fairdatatrain.fairdatastation.api.filter;
+
+import lombok.extern.slf4j.Slf4j;
+import org.springframework.http.HttpHeaders;
+import org.springframework.stereotype.Component;
+import org.springframework.web.bind.annotation.RequestMethod;
+import org.springframework.web.server.ServerWebExchange;
+import org.springframework.web.server.WebFilter;
+import org.springframework.web.server.WebFilterChain;
+import reactor.core.publisher.Mono;
+
+@Slf4j
+@Component
+public class CorsFilter implements WebFilter {
+
+ static final String DELIMITER = ",";
+
+ static final String ALLOWED_METHODS = String.join(
+ DELIMITER,
+ RequestMethod.GET.name(),
+ RequestMethod.DELETE.name(),
+ RequestMethod.PATCH.name(),
+ RequestMethod.POST.name(),
+ RequestMethod.PUT.name()
+ );
+
+ static final String ALLOWED_HEADERS = String.join(
+ DELIMITER,
+ HttpHeaders.ACCEPT,
+ HttpHeaders.AUTHORIZATION,
+ HttpHeaders.CONTENT_TYPE,
+ HttpHeaders.ORIGIN
+ );
+
+ static final String EXPOSED_HEADERS = String.join(
+ DELIMITER,
+ HttpHeaders.LINK,
+ HttpHeaders.LOCATION
+ );
+
+ @Override
+ public Mono filter(ServerWebExchange exchange, WebFilterChain chain) {
+ final HttpHeaders headers = exchange.getResponse().getHeaders();
+ headers.add(HttpHeaders.ACCESS_CONTROL_ALLOW_ORIGIN, "*");
+ headers.add(HttpHeaders.ALLOW, ALLOWED_METHODS);
+ headers.add(HttpHeaders.ACCESS_CONTROL_ALLOW_METHODS, ALLOWED_METHODS);
+ headers.add(HttpHeaders.ACCESS_CONTROL_ALLOW_HEADERS, ALLOWED_HEADERS);
+ headers.add(HttpHeaders.ACCESS_CONTROL_EXPOSE_HEADERS, EXPOSED_HEADERS);
+
+ return chain.filter(exchange);
+ }
+}
diff --git a/src/main/java/org/fairdatatrain/fairdatastation/api/filter/LoggingFilter.java b/src/main/java/org/fairdatatrain/fairdatastation/api/filter/LoggingFilter.java
new file mode 100644
index 0000000..a21e1f7
--- /dev/null
+++ b/src/main/java/org/fairdatatrain/fairdatastation/api/filter/LoggingFilter.java
@@ -0,0 +1,75 @@
+/**
+ * The MIT License
+ * Copyright © 2022 FAIR Data Team
+ *
+ * Permission is hereby granted, free of charge, to any person obtaining a copy
+ * of this software and associated documentation files (the "Software"), to deal
+ * in the Software without restriction, including without limitation the rights
+ * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
+ * copies of the Software, and to permit persons to whom the Software is
+ * furnished to do so, subject to the following conditions:
+ *
+ * The above copyright notice and this permission notice shall be included in
+ * all copies or substantial portions of the Software.
+ *
+ * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
+ * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+ * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
+ * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
+ * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
+ * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
+ * THE SOFTWARE.
+ */
+package org.fairdatatrain.fairdatastation.api.filter;
+
+import lombok.extern.slf4j.Slf4j;
+import org.apache.logging.log4j.ThreadContext;
+import org.springframework.http.HttpHeaders;
+import org.springframework.http.server.reactive.ServerHttpRequest;
+import org.springframework.http.server.reactive.ServerHttpResponse;
+import org.springframework.stereotype.Component;
+import org.springframework.web.server.ServerWebExchange;
+import org.springframework.web.server.WebFilter;
+import org.springframework.web.server.WebFilterChain;
+import reactor.core.publisher.Mono;
+
+import java.util.UUID;
+
+import static java.lang.String.format;
+
+@Slf4j
+@Component
+public class LoggingFilter implements WebFilter {
+
+ @Override
+ public Mono filter(ServerWebExchange exchange, WebFilterChain chain) {
+ final ServerHttpRequest request = exchange.getRequest();
+
+ ThreadContext.put("traceId", UUID.randomUUID().toString());
+ ThreadContext.put("ipAddress", toStringIfNotNull(request.getRemoteAddress()));
+ ThreadContext.put("requestMethod", toStringIfNotNull(request.getMethod()));
+ ThreadContext.put("requestURI", request.getURI().toString());
+
+ log.info(format("%s %s", request.getMethod(), request.getURI()));
+
+ final ServerHttpResponse response = exchange.getResponse();
+
+ ThreadContext.put("responseStatus",
+ toStringIfNotNull(response.getStatusCode()));
+ ThreadContext.put("contentSize",
+ toStringIfNotNull(response.getHeaders().get(HttpHeaders.CONTENT_LENGTH)));
+ log.info(format(
+ "%s %s [Response: %s]",
+ request.getMethod(), request.getURI(), response.getStatusCode())
+ );
+
+ return chain.filter(exchange);
+ }
+
+ private String toStringIfNotNull(Object object) {
+ if (object == null) {
+ return "";
+ }
+ return object.toString();
+ }
+}
diff --git a/src/main/java/org/fairdatatrain/fairdatastation/api/validator/IriValidator.java b/src/main/java/org/fairdatatrain/fairdatastation/api/validator/IriValidator.java
new file mode 100644
index 0000000..c95853e
--- /dev/null
+++ b/src/main/java/org/fairdatatrain/fairdatastation/api/validator/IriValidator.java
@@ -0,0 +1,48 @@
+/**
+ * The MIT License
+ * Copyright © 2022 FAIR Data Team
+ *
+ * Permission is hereby granted, free of charge, to any person obtaining a copy
+ * of this software and associated documentation files (the "Software"), to deal
+ * in the Software without restriction, including without limitation the rights
+ * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
+ * copies of the Software, and to permit persons to whom the Software is
+ * furnished to do so, subject to the following conditions:
+ *
+ * The above copyright notice and this permission notice shall be included in
+ * all copies or substantial portions of the Software.
+ *
+ * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
+ * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+ * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
+ * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
+ * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
+ * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
+ * THE SOFTWARE.
+ */
+package org.fairdatatrain.fairdatastation.api.validator;
+
+import jakarta.validation.ConstraintValidator;
+import jakarta.validation.ConstraintValidatorContext;
+
+import static org.fairdatatrain.fairdatastation.utils.RdfUtils.i;
+
+public class IriValidator implements ConstraintValidator {
+
+ @Override
+ public void initialize(ValidIri text) {
+ }
+
+ @Override
+ public boolean isValid(String text, ConstraintValidatorContext cxt) {
+ try {
+ i(text);
+ return true;
+ }
+ catch (Exception exception) {
+ return false;
+ }
+ }
+
+}
+
diff --git a/src/main/java/org/fairdatatrain/fairdatastation/api/validator/ValidIri.java b/src/main/java/org/fairdatatrain/fairdatastation/api/validator/ValidIri.java
new file mode 100644
index 0000000..adcf099
--- /dev/null
+++ b/src/main/java/org/fairdatatrain/fairdatastation/api/validator/ValidIri.java
@@ -0,0 +1,41 @@
+/**
+ * The MIT License
+ * Copyright © 2022 FAIR Data Team
+ *
+ * Permission is hereby granted, free of charge, to any person obtaining a copy
+ * of this software and associated documentation files (the "Software"), to deal
+ * in the Software without restriction, including without limitation the rights
+ * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
+ * copies of the Software, and to permit persons to whom the Software is
+ * furnished to do so, subject to the following conditions:
+ *
+ * The above copyright notice and this permission notice shall be included in
+ * all copies or substantial portions of the Software.
+ *
+ * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
+ * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+ * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
+ * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
+ * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
+ * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
+ * THE SOFTWARE.
+ */
+package org.fairdatatrain.fairdatastation.api.validator;
+
+import jakarta.validation.Constraint;
+import jakarta.validation.Payload;
+
+import java.lang.annotation.*;
+
+@Documented
+@Constraint(validatedBy = IriValidator.class)
+@Target({ElementType.METHOD, ElementType.FIELD, ElementType.TYPE_USE})
+@Retention(RetentionPolicy.RUNTIME)
+public @interface ValidIri {
+
+ String message() default "Invalid IRI";
+
+ Class>[] groups() default {};
+
+ Class extends Payload>[] payload() default {};
+}
diff --git a/src/main/java/org/fairdatatrain/fairdatastation/config/PropertiesConfig.java b/src/main/java/org/fairdatatrain/fairdatastation/config/PropertiesConfig.java
new file mode 100644
index 0000000..9c705ef
--- /dev/null
+++ b/src/main/java/org/fairdatatrain/fairdatastation/config/PropertiesConfig.java
@@ -0,0 +1,49 @@
+/**
+ * The MIT License
+ * Copyright © 2022 FAIR Data Team
+ *
+ * Permission is hereby granted, free of charge, to any person obtaining a copy
+ * of this software and associated documentation files (the "Software"), to deal
+ * in the Software without restriction, including without limitation the rights
+ * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
+ * copies of the Software, and to permit persons to whom the Software is
+ * furnished to do so, subject to the following conditions:
+ *
+ * The above copyright notice and this permission notice shall be included in
+ * all copies or substantial portions of the Software.
+ *
+ * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
+ * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+ * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
+ * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
+ * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
+ * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
+ * THE SOFTWARE.
+ */
+package org.fairdatatrain.fairdatastation.config;
+
+import org.springframework.context.annotation.Bean;
+import org.springframework.context.annotation.Configuration;
+import org.springframework.context.support.PropertySourcesPlaceholderConfigurer;
+import org.springframework.core.io.ClassPathResource;
+
+@Configuration
+public class PropertiesConfig {
+
+ private static final String GIT_FILE = "META-INF/git.properties";
+
+ private static final String BUILD_INFO_FILE = "META-INF/build-info.properties";
+
+ @Bean
+ public static PropertySourcesPlaceholderConfigurer placeholderConfigurer() {
+ final PropertySourcesPlaceholderConfigurer propsConfig =
+ new PropertySourcesPlaceholderConfigurer();
+ propsConfig.setLocations(
+ new ClassPathResource(GIT_FILE),
+ new ClassPathResource(BUILD_INFO_FILE)
+ );
+ propsConfig.setIgnoreResourceNotFound(true);
+ propsConfig.setIgnoreUnresolvablePlaceholders(true);
+ return propsConfig;
+ }
+}
diff --git a/src/main/java/org/fairdatatrain/fairdatastation/config/RoutingConfig.java b/src/main/java/org/fairdatatrain/fairdatastation/config/RoutingConfig.java
new file mode 100644
index 0000000..23bb30b
--- /dev/null
+++ b/src/main/java/org/fairdatatrain/fairdatastation/config/RoutingConfig.java
@@ -0,0 +1,51 @@
+/**
+ * The MIT License
+ * Copyright © 2022 FAIR Data Team
+ *
+ * Permission is hereby granted, free of charge, to any person obtaining a copy
+ * of this software and associated documentation files (the "Software"), to deal
+ * in the Software without restriction, including without limitation the rights
+ * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
+ * copies of the Software, and to permit persons to whom the Software is
+ * furnished to do so, subject to the following conditions:
+ *
+ * The above copyright notice and this permission notice shall be included in
+ * all copies or substantial portions of the Software.
+ *
+ * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
+ * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+ * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
+ * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
+ * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
+ * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
+ * THE SOFTWARE.
+ */
+package org.fairdatatrain.fairdatastation.config;
+
+import org.springframework.beans.factory.annotation.Value;
+import org.springframework.cloud.gateway.route.RouteLocator;
+import org.springframework.cloud.gateway.route.builder.RouteLocatorBuilder;
+import org.springframework.context.annotation.Bean;
+import org.springframework.context.annotation.Configuration;
+
+@Configuration
+public class RoutingConfig {
+
+ @Value("${data-station.fdp-url}")
+ private String fdpUrl;
+
+ @Bean
+ public RouteLocator customRouteLocator(RouteLocatorBuilder builder) {
+ return builder.routes()
+ .route(
+ "fdp",
+ predicateSpec -> {
+ return predicateSpec
+ .path("/fdp/**")
+ .filters(spec -> spec.stripPrefix(1))
+ .uri(fdpUrl);
+ }
+ )
+ .build();
+ }
+}
diff --git a/src/main/java/org/fairdatatrain/fairdatastation/config/WebClientConfig.java b/src/main/java/org/fairdatatrain/fairdatastation/config/WebClientConfig.java
new file mode 100644
index 0000000..1772f99
--- /dev/null
+++ b/src/main/java/org/fairdatatrain/fairdatastation/config/WebClientConfig.java
@@ -0,0 +1,47 @@
+/**
+ * The MIT License
+ * Copyright © 2022 FAIR Data Team
+ *
+ * Permission is hereby granted, free of charge, to any person obtaining a copy
+ * of this software and associated documentation files (the "Software"), to deal
+ * in the Software without restriction, including without limitation the rights
+ * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
+ * copies of the Software, and to permit persons to whom the Software is
+ * furnished to do so, subject to the following conditions:
+ *
+ * The above copyright notice and this permission notice shall be included in
+ * all copies or substantial portions of the Software.
+ *
+ * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
+ * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+ * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
+ * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
+ * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
+ * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
+ * THE SOFTWARE.
+ */
+package org.fairdatatrain.fairdatastation.config;
+
+import org.springframework.context.annotation.Bean;
+import org.springframework.context.annotation.Configuration;
+import org.springframework.http.client.reactive.ReactorClientHttpConnector;
+import org.springframework.web.reactive.function.client.WebClient;
+import reactor.netty.http.client.HttpClient;
+
+import java.time.Duration;
+
+@Configuration
+public class WebClientConfig {
+ private static final long TIMEOUT = 5 * 60;
+
+ @Bean
+ public WebClient webClient() {
+ final HttpClient client = HttpClient.create()
+ .followRedirect(true)
+ .responseTimeout(Duration.ofSeconds(TIMEOUT));
+
+ return WebClient.builder()
+ .clientConnector(new ReactorClientHttpConnector(client))
+ .build();
+ }
+}
diff --git a/src/main/java/org/fairdatatrain/fairdatastation/config/WebConfig.java b/src/main/java/org/fairdatatrain/fairdatastation/config/WebConfig.java
new file mode 100644
index 0000000..4f9d5da
--- /dev/null
+++ b/src/main/java/org/fairdatatrain/fairdatastation/config/WebConfig.java
@@ -0,0 +1,38 @@
+/**
+ * The MIT License
+ * Copyright © 2022 FAIR Data Team
+ *
+ * Permission is hereby granted, free of charge, to any person obtaining a copy
+ * of this software and associated documentation files (the "Software"), to deal
+ * in the Software without restriction, including without limitation the rights
+ * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
+ * copies of the Software, and to permit persons to whom the Software is
+ * furnished to do so, subject to the following conditions:
+ *
+ * The above copyright notice and this permission notice shall be included in
+ * all copies or substantial portions of the Software.
+ *
+ * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
+ * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+ * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
+ * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
+ * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
+ * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
+ * THE SOFTWARE.
+ */
+package org.fairdatatrain.fairdatastation.config;
+
+import org.springframework.context.annotation.Configuration;
+import org.springframework.data.web.ReactivePageableHandlerMethodArgumentResolver;
+import org.springframework.web.reactive.config.WebFluxConfigurationSupport;
+import org.springframework.web.reactive.result.method.annotation.ArgumentResolverConfigurer;
+
+@Configuration
+public class WebConfig extends WebFluxConfigurationSupport {
+
+ @Override
+ protected void configureArgumentResolvers(ArgumentResolverConfigurer configurer) {
+ configurer.addCustomResolver(new ReactivePageableHandlerMethodArgumentResolver());
+ super.configureArgumentResolvers(configurer);
+ }
+}
diff --git a/src/main/java/org/fairdatatrain/fairdatastation/config/properties/FHIRProperties.java b/src/main/java/org/fairdatatrain/fairdatastation/config/properties/FHIRProperties.java
new file mode 100644
index 0000000..7f8c8a1
--- /dev/null
+++ b/src/main/java/org/fairdatatrain/fairdatastation/config/properties/FHIRProperties.java
@@ -0,0 +1,40 @@
+/**
+ * The MIT License
+ * Copyright © 2022 FAIR Data Team
+ *
+ * Permission is hereby granted, free of charge, to any person obtaining a copy
+ * of this software and associated documentation files (the "Software"), to deal
+ * in the Software without restriction, including without limitation the rights
+ * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
+ * copies of the Software, and to permit persons to whom the Software is
+ * furnished to do so, subject to the following conditions:
+ *
+ * The above copyright notice and this permission notice shall be included in
+ * all copies or substantial portions of the Software.
+ *
+ * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
+ * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+ * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
+ * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
+ * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
+ * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
+ * THE SOFTWARE.
+ */
+package org.fairdatatrain.fairdatastation.config.properties;
+
+import lombok.AllArgsConstructor;
+import lombok.Getter;
+import lombok.NoArgsConstructor;
+import lombok.Setter;
+import org.springframework.boot.context.properties.ConfigurationProperties;
+
+@NoArgsConstructor
+@AllArgsConstructor
+@Getter
+@Setter
+@ConfigurationProperties(prefix = "data-station.apis.fhir")
+public class FHIRProperties {
+
+ private String base;
+
+}
diff --git a/src/main/java/org/fairdatatrain/fairdatastation/config/properties/RepositoryBasicProperties.java b/src/main/java/org/fairdatatrain/fairdatastation/config/properties/RepositoryBasicProperties.java
new file mode 100644
index 0000000..d132895
--- /dev/null
+++ b/src/main/java/org/fairdatatrain/fairdatastation/config/properties/RepositoryBasicProperties.java
@@ -0,0 +1,39 @@
+/**
+ * The MIT License
+ * Copyright © 2022 FAIR Data Team
+ *
+ * Permission is hereby granted, free of charge, to any person obtaining a copy
+ * of this software and associated documentation files (the "Software"), to deal
+ * in the Software without restriction, including without limitation the rights
+ * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
+ * copies of the Software, and to permit persons to whom the Software is
+ * furnished to do so, subject to the following conditions:
+ *
+ * The above copyright notice and this permission notice shall be included in
+ * all copies or substantial portions of the Software.
+ *
+ * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
+ * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+ * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
+ * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
+ * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
+ * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
+ * THE SOFTWARE.
+ */
+package org.fairdatatrain.fairdatastation.config.properties;
+
+import lombok.AllArgsConstructor;
+import lombok.Getter;
+import lombok.NoArgsConstructor;
+import lombok.Setter;
+
+@NoArgsConstructor
+@AllArgsConstructor
+@Getter
+@Setter
+public class RepositoryBasicProperties {
+ private String url = "";
+ private String repository = "";
+ private String username = "";
+ private String password = "";
+}
diff --git a/src/main/java/org/fairdatatrain/fairdatastation/config/properties/RepositoryNativeProperties.java b/src/main/java/org/fairdatatrain/fairdatastation/config/properties/RepositoryNativeProperties.java
new file mode 100644
index 0000000..9049eb6
--- /dev/null
+++ b/src/main/java/org/fairdatatrain/fairdatastation/config/properties/RepositoryNativeProperties.java
@@ -0,0 +1,36 @@
+/**
+ * The MIT License
+ * Copyright © 2022 FAIR Data Team
+ *
+ * Permission is hereby granted, free of charge, to any person obtaining a copy
+ * of this software and associated documentation files (the "Software"), to deal
+ * in the Software without restriction, including without limitation the rights
+ * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
+ * copies of the Software, and to permit persons to whom the Software is
+ * furnished to do so, subject to the following conditions:
+ *
+ * The above copyright notice and this permission notice shall be included in
+ * all copies or substantial portions of the Software.
+ *
+ * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
+ * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+ * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
+ * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
+ * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
+ * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
+ * THE SOFTWARE.
+ */
+package org.fairdatatrain.fairdatastation.config.properties;
+
+import lombok.AllArgsConstructor;
+import lombok.Getter;
+import lombok.NoArgsConstructor;
+import lombok.Setter;
+
+@NoArgsConstructor
+@AllArgsConstructor
+@Getter
+@Setter
+public class RepositoryNativeProperties {
+ private String dir = "";
+}
diff --git a/src/main/java/org/fairdatatrain/fairdatastation/config/properties/RepositoryProperties.java b/src/main/java/org/fairdatatrain/fairdatastation/config/properties/RepositoryProperties.java
new file mode 100644
index 0000000..a8b9222
--- /dev/null
+++ b/src/main/java/org/fairdatatrain/fairdatastation/config/properties/RepositoryProperties.java
@@ -0,0 +1,110 @@
+/**
+ * The MIT License
+ * Copyright © 2022 FAIR Data Team
+ *
+ * Permission is hereby granted, free of charge, to any person obtaining a copy
+ * of this software and associated documentation files (the "Software"), to deal
+ * in the Software without restriction, including without limitation the rights
+ * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
+ * copies of the Software, and to permit persons to whom the Software is
+ * furnished to do so, subject to the following conditions:
+ *
+ * The above copyright notice and this permission notice shall be included in
+ * all copies or substantial portions of the Software.
+ *
+ * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
+ * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+ * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
+ * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
+ * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
+ * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
+ * THE SOFTWARE.
+ */
+package org.fairdatatrain.fairdatastation.config.properties;
+
+import lombok.AllArgsConstructor;
+import lombok.Getter;
+import lombok.NoArgsConstructor;
+import lombok.Setter;
+import org.springframework.boot.context.properties.ConfigurationProperties;
+
+@NoArgsConstructor
+@AllArgsConstructor
+@Getter
+@Setter
+@ConfigurationProperties(prefix = "data-station.storages.triple-store")
+public class RepositoryProperties {
+ // TODO: use polymorphism for types of repository
+
+ public static final int TYPE_IN_MEMORY = 1;
+
+ public static final int TYPE_NATIVE = 2;
+
+ public static final int TYPE_ALLEGRO = 3;
+
+ public static final int TYPE_GRAPHDB = 4;
+
+ public static final int TYPE_BLAZEGRAPH = 5;
+
+ private int type;
+ private RepositoryNativeProperties nativeRepo;
+ private RepositoryBasicProperties agraph;
+ private RepositoryBasicProperties graphDb;
+ private RepositoryBasicProperties blazegraph;
+
+ public void setNative(RepositoryNativeProperties repositoryNativeProperties) {
+ this.nativeRepo = repositoryNativeProperties;
+ }
+
+ public String getStringType() {
+ return switch (type) {
+ case TYPE_IN_MEMORY -> "InMemory";
+ case TYPE_NATIVE -> "Native";
+ case TYPE_ALLEGRO -> "AllegroGraph";
+ case TYPE_GRAPHDB -> "GraphDB";
+ case TYPE_BLAZEGRAPH -> "Blazegraph";
+ default -> "Invalid";
+ };
+ }
+
+ public String getDir() {
+ if (type == TYPE_NATIVE) {
+ return nativeRepo.getDir();
+ }
+ return null;
+ }
+
+ public String getUrl() {
+ return switch (type) {
+ case TYPE_ALLEGRO -> agraph.getUrl();
+ case TYPE_GRAPHDB -> graphDb.getUrl();
+ case TYPE_BLAZEGRAPH -> blazegraph.getUrl();
+ default -> null;
+ };
+ }
+
+ public String getRepository() {
+ return switch (type) {
+ case TYPE_ALLEGRO -> agraph.getRepository();
+ case TYPE_GRAPHDB -> graphDb.getRepository();
+ case TYPE_BLAZEGRAPH -> blazegraph.getRepository();
+ default -> null;
+ };
+ }
+
+ public String getUsername() {
+ return switch (type) {
+ case TYPE_ALLEGRO -> agraph.getUsername();
+ case TYPE_GRAPHDB -> graphDb.getUsername();
+ default -> null;
+ };
+ }
+
+ public String getPassword() {
+ return switch (type) {
+ case TYPE_ALLEGRO -> agraph.getPassword();
+ case TYPE_GRAPHDB -> graphDb.getPassword();
+ default -> null;
+ };
+ }
+}
diff --git a/src/main/java/org/fairdatatrain/fairdatastation/config/storage/RepositoryConfig.java b/src/main/java/org/fairdatatrain/fairdatastation/config/storage/RepositoryConfig.java
new file mode 100644
index 0000000..b13b0b6
--- /dev/null
+++ b/src/main/java/org/fairdatatrain/fairdatastation/config/storage/RepositoryConfig.java
@@ -0,0 +1,163 @@
+/**
+ * The MIT License
+ * Copyright © 2022 FAIR Data Team
+ *
+ * Permission is hereby granted, free of charge, to any person obtaining a copy
+ * of this software and associated documentation files (the "Software"), to deal
+ * in the Software without restriction, including without limitation the rights
+ * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
+ * copies of the Software, and to permit persons to whom the Software is
+ * furnished to do so, subject to the following conditions:
+ *
+ * The above copyright notice and this permission notice shall be included in
+ * all copies or substantial portions of the Software.
+ *
+ * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
+ * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+ * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
+ * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
+ * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
+ * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
+ * THE SOFTWARE.
+ */
+package org.fairdatatrain.fairdatastation.config.storage;
+
+import lombok.extern.slf4j.Slf4j;
+import org.eclipse.rdf4j.repository.Repository;
+import org.eclipse.rdf4j.repository.RepositoryException;
+import org.eclipse.rdf4j.repository.config.RepositoryConfigException;
+import org.eclipse.rdf4j.repository.manager.RemoteRepositoryManager;
+import org.eclipse.rdf4j.repository.manager.RepositoryManager;
+import org.eclipse.rdf4j.repository.sail.SailRepository;
+import org.eclipse.rdf4j.repository.sparql.SPARQLRepository;
+import org.eclipse.rdf4j.sail.Sail;
+import org.eclipse.rdf4j.sail.memory.MemoryStore;
+import org.eclipse.rdf4j.sail.nativerdf.NativeStore;
+import org.fairdatatrain.fairdatastation.config.properties.RepositoryProperties;
+import org.springframework.beans.factory.annotation.Autowired;
+import org.springframework.context.ApplicationContext;
+import org.springframework.context.annotation.Bean;
+import org.springframework.context.annotation.Configuration;
+
+import java.io.File;
+
+import static org.fairdatatrain.fairdatastation.utils.HttpUtils.removeLastSlash;
+
+@Slf4j
+@Configuration
+public class RepositoryConfig {
+
+ @Autowired
+ private RepositoryProperties repositoryProperties;
+
+ @Bean(initMethod = "init", destroyMethod = "shutDown")
+ public Repository repository(ApplicationContext context)
+ throws RepositoryException {
+
+ final Repository repository = switch (repositoryProperties.getType()) {
+ case RepositoryProperties.TYPE_IN_MEMORY -> getInMemoryStore();
+ case RepositoryProperties.TYPE_NATIVE -> getNativeStore();
+ case RepositoryProperties.TYPE_ALLEGRO -> getAgraphRepository();
+ case RepositoryProperties.TYPE_GRAPHDB -> getGraphDBRepository();
+ case RepositoryProperties.TYPE_BLAZEGRAPH -> getBlazeGraphRepository();
+ default -> null;
+ };
+
+ if (repository == null) {
+ log.info("RDF repository is not configured");
+ }
+ else {
+ log.info("Successfully configure a RDF repository");
+ }
+ return repository;
+ }
+
+ private Repository getInMemoryStore() {
+ log.info("Setting up InMemory Store");
+ final Sail store = new MemoryStore();
+ return new SailRepository(store);
+ }
+
+ private Repository getNativeStore() {
+ log.info("Setting up Native Store");
+ if (!repositoryProperties.getNativeRepo().getDir().isEmpty()) {
+ final File dataDir = new File(repositoryProperties.getNativeRepo().getDir());
+ return new SailRepository(new NativeStore(dataDir));
+ }
+ log.warn("'repository.native.dir' is empty");
+ return null;
+ }
+
+ private Repository getAgraphRepository() {
+ log.info("Setting up Allegro Graph Store");
+ if (!repositoryProperties.getAgraph().getUrl().isEmpty()) {
+ final SPARQLRepository repository =
+ new SPARQLRepository(repositoryProperties.getAgraph().getUrl());
+ if (!repositoryProperties.getAgraph().getUsername().isEmpty()
+ && !repositoryProperties.getAgraph().getPassword().isEmpty()) {
+ repository.setUsernameAndPassword(
+ repositoryProperties.getAgraph().getUsername(),
+ repositoryProperties.getAgraph().getPassword()
+ );
+ }
+ return repository;
+ }
+ log.warn("'repository.agraph.url' is empty");
+ return null;
+ }
+
+ private Repository getBlazeGraphRepository() {
+ log.info("Setting up Blaze Graph Store");
+ String blazegraphUrl = repositoryProperties.getBlazegraph().getUrl();
+ if (!blazegraphUrl.isEmpty()) {
+ blazegraphUrl = removeLastSlash(blazegraphUrl);
+ // Build url for blazegraph (Eg: http://localhost:8079/bigdata/namespace/test1/sparql)
+ final StringBuilder urlBuilder = new StringBuilder();
+ urlBuilder.append(blazegraphUrl);
+ urlBuilder.append("/namespace/");
+ if (!repositoryProperties.getBlazegraph().getRepository().isEmpty()) {
+ urlBuilder.append(repositoryProperties.getBlazegraph().getRepository());
+ }
+ else {
+ urlBuilder.append("kb");
+ }
+ urlBuilder.append("/sparql");
+ return new SPARQLRepository(urlBuilder.toString());
+ }
+ log.warn("'repository.blazegraph.url' is empty");
+ return null;
+ }
+
+ private Repository getGraphDBRepository() {
+ log.info("Setting up GraphDB Store");
+ try {
+ System.setProperty("org.eclipse.rdf4j.rio.binary.format_version", "1");
+ if (!repositoryProperties.getGraphDb().getUrl().isEmpty()
+ && !repositoryProperties.getGraphDb().getRepository().isEmpty()) {
+ final RepositoryManager repositoryManager;
+ if (!repositoryProperties.getGraphDb().getUsername().isEmpty()
+ && !repositoryProperties.getGraphDb().getPassword().isEmpty()) {
+ repositoryManager = RemoteRepositoryManager.getInstance(
+ repositoryProperties.getGraphDb().getUrl(),
+ repositoryProperties.getGraphDb().getUsername(),
+ repositoryProperties.getGraphDb().getPassword()
+ );
+ }
+ else {
+ repositoryManager = RemoteRepositoryManager.getInstance(
+ repositoryProperties.getGraphDb().getUrl()
+ );
+ }
+ return repositoryManager.getRepository(
+ repositoryProperties.getGraphDb().getRepository()
+ );
+ }
+ log.warn("'repository.graphDb.url' or 'repository.graphDb.repository' is empty");
+ }
+ catch (RepositoryConfigException | RepositoryException exception) {
+ log.error("Failed to connect to GraphDB");
+ }
+ return null;
+ }
+
+}
diff --git a/src/main/java/org/fairdatatrain/fairdatastation/data/model/base/BaseEntity.java b/src/main/java/org/fairdatatrain/fairdatastation/data/model/base/BaseEntity.java
new file mode 100644
index 0000000..3656618
--- /dev/null
+++ b/src/main/java/org/fairdatatrain/fairdatastation/data/model/base/BaseEntity.java
@@ -0,0 +1,79 @@
+/**
+ * The MIT License
+ * Copyright © 2022 FAIR Data Team
+ *
+ * Permission is hereby granted, free of charge, to any person obtaining a copy
+ * of this software and associated documentation files (the "Software"), to deal
+ * in the Software without restriction, including without limitation the rights
+ * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
+ * copies of the Software, and to permit persons to whom the Software is
+ * furnished to do so, subject to the following conditions:
+ *
+ * The above copyright notice and this permission notice shall be included in
+ * all copies or substantial portions of the Software.
+ *
+ * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
+ * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+ * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
+ * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
+ * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
+ * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
+ * THE SOFTWARE.
+ */
+package org.fairdatatrain.fairdatastation.data.model.base;
+
+import jakarta.persistence.*;
+import jakarta.validation.constraints.NotNull;
+import lombok.AllArgsConstructor;
+import lombok.Getter;
+import lombok.NoArgsConstructor;
+import lombok.Setter;
+import lombok.experimental.SuperBuilder;
+import org.hibernate.annotations.CreationTimestamp;
+import org.hibernate.annotations.UpdateTimestamp;
+
+import java.sql.Timestamp;
+import java.util.Objects;
+import java.util.UUID;
+
+@MappedSuperclass
+@SuperBuilder(toBuilder = true)
+@Getter
+@Setter
+@AllArgsConstructor
+@NoArgsConstructor
+public class BaseEntity {
+
+ @Id
+ @GeneratedValue(strategy = GenerationType.AUTO)
+ @NotNull
+ @Column(name = "uuid", nullable = false, updatable = false)
+ private UUID uuid;
+
+ @CreationTimestamp
+ @NotNull
+ @Column(name = "created_at", nullable = false, updatable = false)
+ private Timestamp createdAt;
+
+ @UpdateTimestamp
+ @NotNull
+ @Column(name = "updated_at", nullable = false)
+ private Timestamp updatedAt;
+
+ @Override
+ public boolean equals(Object o) {
+ if (this == o) {
+ return true;
+ }
+ if (o == null || getClass() != o.getClass()) {
+ return false;
+ }
+ final BaseEntity that = (BaseEntity) o;
+ return uuid.equals(that.uuid);
+ }
+
+ @Override
+ public int hashCode() {
+ return Objects.hash(uuid);
+ }
+}
diff --git a/src/main/java/org/fairdatatrain/fairdatastation/data/model/enums/ArtifactStorage.java b/src/main/java/org/fairdatatrain/fairdatastation/data/model/enums/ArtifactStorage.java
new file mode 100644
index 0000000..14660c5
--- /dev/null
+++ b/src/main/java/org/fairdatatrain/fairdatastation/data/model/enums/ArtifactStorage.java
@@ -0,0 +1,29 @@
+/**
+ * The MIT License
+ * Copyright © 2022 FAIR Data Team
+ *
+ * Permission is hereby granted, free of charge, to any person obtaining a copy
+ * of this software and associated documentation files (the "Software"), to deal
+ * in the Software without restriction, including without limitation the rights
+ * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
+ * copies of the Software, and to permit persons to whom the Software is
+ * furnished to do so, subject to the following conditions:
+ *
+ * The above copyright notice and this permission notice shall be included in
+ * all copies or substantial portions of the Software.
+ *
+ * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
+ * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+ * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
+ * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
+ * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
+ * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
+ * THE SOFTWARE.
+ */
+package org.fairdatatrain.fairdatastation.data.model.enums;
+
+public enum ArtifactStorage {
+ POSTGRES,
+ S3,
+ LOCALFS
+}
diff --git a/src/main/java/org/fairdatatrain/fairdatastation/data/model/enums/JobStatus.java b/src/main/java/org/fairdatatrain/fairdatastation/data/model/enums/JobStatus.java
new file mode 100644
index 0000000..8730cf5
--- /dev/null
+++ b/src/main/java/org/fairdatatrain/fairdatastation/data/model/enums/JobStatus.java
@@ -0,0 +1,34 @@
+/**
+ * The MIT License
+ * Copyright © 2022 FAIR Data Team
+ *
+ * Permission is hereby granted, free of charge, to any person obtaining a copy
+ * of this software and associated documentation files (the "Software"), to deal
+ * in the Software without restriction, including without limitation the rights
+ * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
+ * copies of the Software, and to permit persons to whom the Software is
+ * furnished to do so, subject to the following conditions:
+ *
+ * The above copyright notice and this permission notice shall be included in
+ * all copies or substantial portions of the Software.
+ *
+ * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
+ * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+ * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
+ * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
+ * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
+ * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
+ * THE SOFTWARE.
+ */
+package org.fairdatatrain.fairdatastation.data.model.enums;
+
+public enum JobStatus {
+ PREPARED,
+ QUEUED,
+ RUNNING,
+ FINISHED,
+ ABORTING,
+ ABORTED,
+ ERRORED,
+ FAILED
+}
diff --git a/src/main/java/org/fairdatatrain/fairdatastation/data/model/event/EventDelivery.java b/src/main/java/org/fairdatatrain/fairdatastation/data/model/event/EventDelivery.java
new file mode 100644
index 0000000..9234b7d
--- /dev/null
+++ b/src/main/java/org/fairdatatrain/fairdatastation/data/model/event/EventDelivery.java
@@ -0,0 +1,74 @@
+/**
+ * The MIT License
+ * Copyright © 2022 FAIR Data Team
+ *
+ * Permission is hereby granted, free of charge, to any person obtaining a copy
+ * of this software and associated documentation files (the "Software"), to deal
+ * in the Software without restriction, including without limitation the rights
+ * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
+ * copies of the Software, and to permit persons to whom the Software is
+ * furnished to do so, subject to the following conditions:
+ *
+ * The above copyright notice and this permission notice shall be included in
+ * all copies or substantial portions of the Software.
+ *
+ * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
+ * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+ * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
+ * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
+ * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
+ * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
+ * THE SOFTWARE.
+ */
+package org.fairdatatrain.fairdatastation.data.model.event;
+
+import jakarta.persistence.*;
+import jakarta.validation.constraints.NotNull;
+import lombok.AllArgsConstructor;
+import lombok.Getter;
+import lombok.NoArgsConstructor;
+import lombok.Setter;
+import lombok.experimental.SuperBuilder;
+import org.fairdatatrain.fairdatastation.data.model.base.BaseEntity;
+
+import java.sql.Timestamp;
+
+@Entity(name = "EventDelivery")
+@Table(name = "event_delivery")
+@Getter
+@Setter
+@AllArgsConstructor
+@NoArgsConstructor
+@SuperBuilder(toBuilder = true)
+public class EventDelivery extends BaseEntity {
+
+ @NotNull
+ @Column(name = "delivered", nullable = false)
+ private Boolean delivered;
+
+ @NotNull
+ @Column(name = "message", nullable = false)
+ private String message;
+
+ @Column(name = "dispatch_at", nullable = false)
+ private Timestamp dispatchAt;
+
+ @Column(name = "dispatched_at")
+ private Timestamp dispatchedAt;
+
+ @NotNull
+ @Column(name = "retry_number", nullable = false)
+ private Integer retryNumber;
+
+ @NotNull
+ @Column(name = "priority", nullable = false)
+ private Integer priority;
+
+ @ManyToOne
+ @JoinColumn(name = "job_artifact_id")
+ private JobArtifact jobArtifact;
+
+ @ManyToOne
+ @JoinColumn(name = "job_event_id")
+ private JobEvent jobEvent;
+}
diff --git a/src/main/java/org/fairdatatrain/fairdatastation/data/model/event/Job.java b/src/main/java/org/fairdatatrain/fairdatastation/data/model/event/Job.java
new file mode 100644
index 0000000..519a31c
--- /dev/null
+++ b/src/main/java/org/fairdatatrain/fairdatastation/data/model/event/Job.java
@@ -0,0 +1,81 @@
+/**
+ * The MIT License
+ * Copyright © 2022 FAIR Data Team
+ *
+ * Permission is hereby granted, free of charge, to any person obtaining a copy
+ * of this software and associated documentation files (the "Software"), to deal
+ * in the Software without restriction, including without limitation the rights
+ * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
+ * copies of the Software, and to permit persons to whom the Software is
+ * furnished to do so, subject to the following conditions:
+ *
+ * The above copyright notice and this permission notice shall be included in
+ * all copies or substantial portions of the Software.
+ *
+ * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
+ * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+ * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
+ * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
+ * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
+ * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
+ * THE SOFTWARE.
+ */
+package org.fairdatatrain.fairdatastation.data.model.event;
+
+import jakarta.persistence.*;
+import jakarta.validation.constraints.NotNull;
+import lombok.AllArgsConstructor;
+import lombok.Getter;
+import lombok.NoArgsConstructor;
+import lombok.Setter;
+import lombok.experimental.SuperBuilder;
+import org.fairdatatrain.fairdatastation.data.model.base.BaseEntity;
+import org.fairdatatrain.fairdatastation.data.model.enums.JobStatus;
+
+import java.sql.Timestamp;
+import java.util.List;
+
+@Entity(name = "Job")
+@Table(name = "job")
+@Getter
+@Setter
+@AllArgsConstructor
+@NoArgsConstructor
+@SuperBuilder(toBuilder = true)
+public class Job extends BaseEntity {
+
+ @NotNull
+ @Column(name = "secret", nullable = false)
+ private String secret;
+
+ @Column(name = "remote_id")
+ private String remoteId;
+
+ @Enumerated(EnumType.STRING)
+ @Column(name = "status", columnDefinition = "job_status", nullable = false)
+ private JobStatus status;
+
+ @Column(name = "started_at")
+ private Timestamp startedAt;
+
+ @Column(name = "finished_at")
+ private Timestamp finishedAt;
+
+ @Column(name = "callback_event")
+ private String callbackEvent;
+
+ @Column(name = "callback_artifact")
+ private String callbackArtifact;
+
+ @Column(name = "train_uri")
+ private String trainUri;
+
+ @Column(name = "version", nullable = false)
+ private Long version;
+
+ @OneToMany(fetch = FetchType.LAZY, mappedBy = "job")
+ private List events;
+
+ @OneToMany(fetch = FetchType.LAZY, mappedBy = "job")
+ private List artifacts;
+}
diff --git a/src/main/java/org/fairdatatrain/fairdatastation/data/model/event/JobArtifact.java b/src/main/java/org/fairdatatrain/fairdatastation/data/model/event/JobArtifact.java
new file mode 100644
index 0000000..5ea038b
--- /dev/null
+++ b/src/main/java/org/fairdatatrain/fairdatastation/data/model/event/JobArtifact.java
@@ -0,0 +1,86 @@
+/**
+ * The MIT License
+ * Copyright © 2022 FAIR Data Team
+ *
+ * Permission is hereby granted, free of charge, to any person obtaining a copy
+ * of this software and associated documentation files (the "Software"), to deal
+ * in the Software without restriction, including without limitation the rights
+ * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
+ * copies of the Software, and to permit persons to whom the Software is
+ * furnished to do so, subject to the following conditions:
+ *
+ * The above copyright notice and this permission notice shall be included in
+ * all copies or substantial portions of the Software.
+ *
+ * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
+ * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+ * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
+ * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
+ * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
+ * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
+ * THE SOFTWARE.
+ */
+package org.fairdatatrain.fairdatastation.data.model.event;
+
+import jakarta.persistence.*;
+import jakarta.validation.constraints.NotNull;
+import lombok.AllArgsConstructor;
+import lombok.Getter;
+import lombok.NoArgsConstructor;
+import lombok.Setter;
+import lombok.experimental.SuperBuilder;
+import org.fairdatatrain.fairdatastation.data.model.base.BaseEntity;
+import org.fairdatatrain.fairdatastation.data.model.enums.ArtifactStorage;
+
+import java.sql.Timestamp;
+import java.util.List;
+
+@Entity(name = "JobArtifact")
+@Table(name = "job_artifact")
+@Getter
+@Setter
+@AllArgsConstructor
+@NoArgsConstructor
+@SuperBuilder(toBuilder = true)
+public class JobArtifact extends BaseEntity {
+
+ @NotNull
+ @Column(name = "display_name")
+ private String displayName;
+
+ @NotNull
+ @Column(name = "filename", nullable = false)
+ private String filename;
+
+ @NotNull
+ @Column(name = "bytesize", nullable = false)
+ private Long bytesize;
+
+ @NotNull
+ @Column(name = "hash", nullable = false)
+ private String hash;
+
+ @NotNull
+ @Column(name = "content_type", nullable = false)
+ private String contentType;
+
+ @NotNull
+ @Enumerated(EnumType.STRING)
+ @Column(name = "storage", columnDefinition = "artifact_storage", nullable = false)
+ private ArtifactStorage storage;
+
+ @NotNull
+ @Column(name = "occurred_at", nullable = false)
+ private Timestamp occurredAt;
+
+ @Column(name = "data", columnDefinition = "BLOB NOT NULL", nullable = false)
+ private byte[] data;
+
+ @NotNull
+ @ManyToOne
+ @JoinColumn(name = "job_id", nullable = false)
+ private Job job;
+
+ @OneToMany(fetch = FetchType.LAZY, mappedBy = "jobArtifact")
+ private List deliveries;
+}
diff --git a/src/main/java/org/fairdatatrain/fairdatastation/data/model/event/JobEvent.java b/src/main/java/org/fairdatatrain/fairdatastation/data/model/event/JobEvent.java
new file mode 100644
index 0000000..6111032
--- /dev/null
+++ b/src/main/java/org/fairdatatrain/fairdatastation/data/model/event/JobEvent.java
@@ -0,0 +1,66 @@
+/**
+ * The MIT License
+ * Copyright © 2022 FAIR Data Team
+ *
+ * Permission is hereby granted, free of charge, to any person obtaining a copy
+ * of this software and associated documentation files (the "Software"), to deal
+ * in the Software without restriction, including without limitation the rights
+ * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
+ * copies of the Software, and to permit persons to whom the Software is
+ * furnished to do so, subject to the following conditions:
+ *
+ * The above copyright notice and this permission notice shall be included in
+ * all copies or substantial portions of the Software.
+ *
+ * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
+ * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+ * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
+ * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
+ * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
+ * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
+ * THE SOFTWARE.
+ */
+package org.fairdatatrain.fairdatastation.data.model.event;
+
+import jakarta.persistence.*;
+import jakarta.validation.constraints.NotNull;
+import lombok.AllArgsConstructor;
+import lombok.Getter;
+import lombok.NoArgsConstructor;
+import lombok.Setter;
+import lombok.experimental.SuperBuilder;
+import org.fairdatatrain.fairdatastation.data.model.base.BaseEntity;
+import org.fairdatatrain.fairdatastation.data.model.enums.JobStatus;
+
+import java.sql.Timestamp;
+import java.util.List;
+
+@Entity(name = "JobEvent")
+@Table(name = "job_event")
+@Getter
+@Setter
+@AllArgsConstructor
+@NoArgsConstructor
+@SuperBuilder(toBuilder = true)
+public class JobEvent extends BaseEntity {
+
+ @Enumerated(EnumType.STRING)
+ @Column(name = "result_status", columnDefinition = "job_status")
+ private JobStatus resultStatus;
+
+ @NotNull
+ @Column(name = "occurred_at", nullable = false)
+ private Timestamp occurredAt;
+
+ @NotNull
+ @Column(name = "message", nullable = false)
+ private String message;
+
+ @NotNull
+ @ManyToOne
+ @JoinColumn(name = "job_id", nullable = false)
+ private Job job;
+
+ @OneToMany(fetch = FetchType.LAZY, mappedBy = "jobEvent")
+ private List deliveries;
+}
diff --git a/src/main/java/org/fairdatatrain/fairdatastation/data/repository/base/BaseRepository.java b/src/main/java/org/fairdatatrain/fairdatastation/data/repository/base/BaseRepository.java
new file mode 100644
index 0000000..2ea0e36
--- /dev/null
+++ b/src/main/java/org/fairdatatrain/fairdatastation/data/repository/base/BaseRepository.java
@@ -0,0 +1,32 @@
+/**
+ * The MIT License
+ * Copyright © 2022 FAIR Data Team
+ *
+ * Permission is hereby granted, free of charge, to any person obtaining a copy
+ * of this software and associated documentation files (the "Software"), to deal
+ * in the Software without restriction, including without limitation the rights
+ * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
+ * copies of the Software, and to permit persons to whom the Software is
+ * furnished to do so, subject to the following conditions:
+ *
+ * The above copyright notice and this permission notice shall be included in
+ * all copies or substantial portions of the Software.
+ *
+ * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
+ * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+ * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
+ * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
+ * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
+ * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
+ * THE SOFTWARE.
+ */
+package org.fairdatatrain.fairdatastation.data.repository.base;
+
+import org.springframework.data.jpa.repository.JpaRepository;
+import org.springframework.data.repository.NoRepositoryBean;
+
+import java.util.UUID;
+
+@NoRepositoryBean
+public interface BaseRepository extends JpaRepository {
+}
diff --git a/src/main/java/org/fairdatatrain/fairdatastation/data/repository/event/EventDeliveryRepository.java b/src/main/java/org/fairdatatrain/fairdatastation/data/repository/event/EventDeliveryRepository.java
new file mode 100644
index 0000000..3af8791
--- /dev/null
+++ b/src/main/java/org/fairdatatrain/fairdatastation/data/repository/event/EventDeliveryRepository.java
@@ -0,0 +1,45 @@
+/**
+ * The MIT License
+ * Copyright © 2022 FAIR Data Team
+ *
+ * Permission is hereby granted, free of charge, to any person obtaining a copy
+ * of this software and associated documentation files (the "Software"), to deal
+ * in the Software without restriction, including without limitation the rights
+ * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
+ * copies of the Software, and to permit persons to whom the Software is
+ * furnished to do so, subject to the following conditions:
+ *
+ * The above copyright notice and this permission notice shall be included in
+ * all copies or substantial portions of the Software.
+ *
+ * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
+ * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+ * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
+ * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
+ * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
+ * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
+ * THE SOFTWARE.
+ */
+package org.fairdatatrain.fairdatastation.data.repository.event;
+
+import org.fairdatatrain.fairdatastation.data.model.event.EventDelivery;
+import org.fairdatatrain.fairdatastation.data.repository.base.BaseRepository;
+import org.springframework.data.jpa.repository.Query;
+import org.springframework.data.repository.query.Param;
+import org.springframework.stereotype.Repository;
+
+import java.sql.Timestamp;
+import java.util.List;
+
+@Repository
+public interface EventDeliveryRepository extends BaseRepository {
+
+ @Query(value = """
+ SELECT * FROM event_delivery
+ WHERE dispatch_at < :ts AND delivered = false AND dispatched_at IS NULL
+ ORDER BY dispatch_at ASC, priority DESC
+ """,
+ nativeQuery = true
+ )
+ List getNextEventDeliveries(@Param("ts") Timestamp timestamp);
+}
diff --git a/src/main/java/org/fairdatatrain/fairdatastation/data/repository/event/JobArtifactRepository.java b/src/main/java/org/fairdatatrain/fairdatastation/data/repository/event/JobArtifactRepository.java
new file mode 100644
index 0000000..5834068
--- /dev/null
+++ b/src/main/java/org/fairdatatrain/fairdatastation/data/repository/event/JobArtifactRepository.java
@@ -0,0 +1,31 @@
+/**
+ * The MIT License
+ * Copyright © 2022 FAIR Data Team
+ *
+ * Permission is hereby granted, free of charge, to any person obtaining a copy
+ * of this software and associated documentation files (the "Software"), to deal
+ * in the Software without restriction, including without limitation the rights
+ * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
+ * copies of the Software, and to permit persons to whom the Software is
+ * furnished to do so, subject to the following conditions:
+ *
+ * The above copyright notice and this permission notice shall be included in
+ * all copies or substantial portions of the Software.
+ *
+ * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
+ * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+ * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
+ * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
+ * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
+ * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
+ * THE SOFTWARE.
+ */
+package org.fairdatatrain.fairdatastation.data.repository.event;
+
+import org.fairdatatrain.fairdatastation.data.model.event.JobArtifact;
+import org.fairdatatrain.fairdatastation.data.repository.base.BaseRepository;
+import org.springframework.stereotype.Repository;
+
+@Repository
+public interface JobArtifactRepository extends BaseRepository {
+}
diff --git a/src/main/java/org/fairdatatrain/fairdatastation/data/repository/event/JobEventRepository.java b/src/main/java/org/fairdatatrain/fairdatastation/data/repository/event/JobEventRepository.java
new file mode 100644
index 0000000..7557233
--- /dev/null
+++ b/src/main/java/org/fairdatatrain/fairdatastation/data/repository/event/JobEventRepository.java
@@ -0,0 +1,31 @@
+/**
+ * The MIT License
+ * Copyright © 2022 FAIR Data Team
+ *
+ * Permission is hereby granted, free of charge, to any person obtaining a copy
+ * of this software and associated documentation files (the "Software"), to deal
+ * in the Software without restriction, including without limitation the rights
+ * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
+ * copies of the Software, and to permit persons to whom the Software is
+ * furnished to do so, subject to the following conditions:
+ *
+ * The above copyright notice and this permission notice shall be included in
+ * all copies or substantial portions of the Software.
+ *
+ * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
+ * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+ * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
+ * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
+ * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
+ * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
+ * THE SOFTWARE.
+ */
+package org.fairdatatrain.fairdatastation.data.repository.event;
+
+import org.fairdatatrain.fairdatastation.data.model.event.JobEvent;
+import org.fairdatatrain.fairdatastation.data.repository.base.BaseRepository;
+import org.springframework.stereotype.Repository;
+
+@Repository
+public interface JobEventRepository extends BaseRepository {
+}
diff --git a/src/main/java/org/fairdatatrain/fairdatastation/data/repository/event/JobRepository.java b/src/main/java/org/fairdatatrain/fairdatastation/data/repository/event/JobRepository.java
new file mode 100644
index 0000000..33e5ec6
--- /dev/null
+++ b/src/main/java/org/fairdatatrain/fairdatastation/data/repository/event/JobRepository.java
@@ -0,0 +1,35 @@
+/**
+ * The MIT License
+ * Copyright © 2022 FAIR Data Team
+ *
+ * Permission is hereby granted, free of charge, to any person obtaining a copy
+ * of this software and associated documentation files (the "Software"), to deal
+ * in the Software without restriction, including without limitation the rights
+ * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
+ * copies of the Software, and to permit persons to whom the Software is
+ * furnished to do so, subject to the following conditions:
+ *
+ * The above copyright notice and this permission notice shall be included in
+ * all copies or substantial portions of the Software.
+ *
+ * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
+ * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+ * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
+ * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
+ * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
+ * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
+ * THE SOFTWARE.
+ */
+package org.fairdatatrain.fairdatastation.data.repository.event;
+
+import org.fairdatatrain.fairdatastation.data.model.event.Job;
+import org.fairdatatrain.fairdatastation.data.repository.base.BaseRepository;
+import org.springframework.stereotype.Repository;
+
+import java.util.Optional;
+
+@Repository
+public interface JobRepository extends BaseRepository {
+
+ Optional findFirstByFinishedAtIsNullOrderByCreatedAtAsc();
+}
diff --git a/src/main/java/org/fairdatatrain/fairdatastation/exception/NotFoundException.java b/src/main/java/org/fairdatatrain/fairdatastation/exception/NotFoundException.java
new file mode 100644
index 0000000..71594ab
--- /dev/null
+++ b/src/main/java/org/fairdatatrain/fairdatastation/exception/NotFoundException.java
@@ -0,0 +1,43 @@
+/**
+ * The MIT License
+ * Copyright © 2022 FAIR Data Team
+ *
+ * Permission is hereby granted, free of charge, to any person obtaining a copy
+ * of this software and associated documentation files (the "Software"), to deal
+ * in the Software without restriction, including without limitation the rights
+ * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
+ * copies of the Software, and to permit persons to whom the Software is
+ * furnished to do so, subject to the following conditions:
+ *
+ * The above copyright notice and this permission notice shall be included in
+ * all copies or substantial portions of the Software.
+ *
+ * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
+ * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+ * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
+ * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
+ * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
+ * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
+ * THE SOFTWARE.
+ */
+package org.fairdatatrain.fairdatastation.exception;
+
+import lombok.AllArgsConstructor;
+import lombok.Getter;
+
+import java.util.Map;
+import java.util.UUID;
+
+@AllArgsConstructor
+@Getter
+public class NotFoundException extends Exception {
+
+ private final String entityName;
+
+ private final Map fields;
+
+ public NotFoundException(String entityName, UUID uuid) {
+ this.entityName = entityName;
+ this.fields = Map.of("uuid", uuid.toString());
+ }
+}
diff --git a/src/main/java/org/fairdatatrain/fairdatastation/exception/StorageException.java b/src/main/java/org/fairdatatrain/fairdatastation/exception/StorageException.java
new file mode 100644
index 0000000..30839b4
--- /dev/null
+++ b/src/main/java/org/fairdatatrain/fairdatastation/exception/StorageException.java
@@ -0,0 +1,33 @@
+/**
+ * The MIT License
+ * Copyright © 2022 FAIR Data Team
+ *
+ * Permission is hereby granted, free of charge, to any person obtaining a copy
+ * of this software and associated documentation files (the "Software"), to deal
+ * in the Software without restriction, including without limitation the rights
+ * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
+ * copies of the Software, and to permit persons to whom the Software is
+ * furnished to do so, subject to the following conditions:
+ *
+ * The above copyright notice and this permission notice shall be included in
+ * all copies or substantial portions of the Software.
+ *
+ * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
+ * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+ * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
+ * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
+ * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
+ * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
+ * THE SOFTWARE.
+ */
+package org.fairdatatrain.fairdatastation.exception;
+
+import lombok.AllArgsConstructor;
+import lombok.Getter;
+
+@AllArgsConstructor
+@Getter
+public class StorageException extends Exception {
+
+ private final String message;
+}
diff --git a/src/main/java/org/fairdatatrain/fairdatastation/gateway/filters/LoggingGlobalFilter.java b/src/main/java/org/fairdatatrain/fairdatastation/gateway/filters/LoggingGlobalFilter.java
new file mode 100644
index 0000000..9fdf931
--- /dev/null
+++ b/src/main/java/org/fairdatatrain/fairdatastation/gateway/filters/LoggingGlobalFilter.java
@@ -0,0 +1,60 @@
+/**
+ * The MIT License
+ * Copyright © 2022 FAIR Data Team
+ *
+ * Permission is hereby granted, free of charge, to any person obtaining a copy
+ * of this software and associated documentation files (the "Software"), to deal
+ * in the Software without restriction, including without limitation the rights
+ * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
+ * copies of the Software, and to permit persons to whom the Software is
+ * furnished to do so, subject to the following conditions:
+ *
+ * The above copyright notice and this permission notice shall be included in
+ * all copies or substantial portions of the Software.
+ *
+ * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
+ * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+ * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
+ * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
+ * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
+ * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
+ * THE SOFTWARE.
+ */
+package org.fairdatatrain.fairdatastation.gateway.filters;
+
+import lombok.extern.slf4j.Slf4j;
+import org.springframework.cloud.gateway.filter.GatewayFilterChain;
+import org.springframework.cloud.gateway.filter.GlobalFilter;
+import org.springframework.cloud.gateway.route.Route;
+import org.springframework.core.Ordered;
+import org.springframework.stereotype.Component;
+import org.springframework.web.server.ServerWebExchange;
+import reactor.core.publisher.Mono;
+
+import static java.lang.String.format;
+import static org.springframework.cloud.gateway.support.ServerWebExchangeUtils.GATEWAY_ROUTE_ATTR;
+
+@Slf4j
+@Component
+public class LoggingGlobalFilter implements GlobalFilter, Ordered {
+
+ @Override
+ public Mono filter(ServerWebExchange exchange, GatewayFilterChain chain) {
+ final Route route = exchange.getAttribute(GATEWAY_ROUTE_ATTR);
+ log.info(format("Routing: %s -> [%s]", exchange.getRequest().getPath(), route.getId()));
+ return chain.filter(exchange)
+ .then(Mono.fromRunnable(() -> {
+ log.info(format(
+ "Routing: %s -> [%s] %s",
+ exchange.getRequest().getPath(),
+ route.getId(),
+ exchange.getResponse().getStatusCode()
+ ));
+ }));
+ }
+
+ @Override
+ public int getOrder() {
+ return -1;
+ }
+}
diff --git a/src/main/java/org/fairdatatrain/fairdatastation/service/accesscontrol/BasicAccessControlService.java b/src/main/java/org/fairdatatrain/fairdatastation/service/accesscontrol/BasicAccessControlService.java
new file mode 100644
index 0000000..515b7fb
--- /dev/null
+++ b/src/main/java/org/fairdatatrain/fairdatastation/service/accesscontrol/BasicAccessControlService.java
@@ -0,0 +1,38 @@
+/**
+ * The MIT License
+ * Copyright © 2022 FAIR Data Team
+ *
+ * Permission is hereby granted, free of charge, to any person obtaining a copy
+ * of this software and associated documentation files (the "Software"), to deal
+ * in the Software without restriction, including without limitation the rights
+ * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
+ * copies of the Software, and to permit persons to whom the Software is
+ * furnished to do so, subject to the following conditions:
+ *
+ * The above copyright notice and this permission notice shall be included in
+ * all copies or substantial portions of the Software.
+ *
+ * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
+ * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+ * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
+ * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
+ * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
+ * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
+ * THE SOFTWARE.
+ */
+package org.fairdatatrain.fairdatastation.service.accesscontrol;
+
+import lombok.SneakyThrows;
+import lombok.extern.slf4j.Slf4j;
+import org.springframework.stereotype.Service;
+
+@Slf4j
+@Service
+public class BasicAccessControlService {
+ // TODO: common interface for different implementations?
+
+ @SneakyThrows
+ public void checkAccess() {
+ // TODO: arguments? some basic check?
+ }
+}
diff --git a/src/main/java/org/fairdatatrain/fairdatastation/service/actuator/AppInfoContributor.java b/src/main/java/org/fairdatatrain/fairdatastation/service/actuator/AppInfoContributor.java
new file mode 100644
index 0000000..392746f
--- /dev/null
+++ b/src/main/java/org/fairdatatrain/fairdatastation/service/actuator/AppInfoContributor.java
@@ -0,0 +1,62 @@
+/**
+ * The MIT License
+ * Copyright © 2022 FAIR Data Team
+ *
+ * Permission is hereby granted, free of charge, to any person obtaining a copy
+ * of this software and associated documentation files (the "Software"), to deal
+ * in the Software without restriction, including without limitation the rights
+ * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
+ * copies of the Software, and to permit persons to whom the Software is
+ * furnished to do so, subject to the following conditions:
+ *
+ * The above copyright notice and this permission notice shall be included in
+ * all copies or substantial portions of the Software.
+ *
+ * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
+ * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+ * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
+ * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
+ * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
+ * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
+ * THE SOFTWARE.
+ */
+package org.fairdatatrain.fairdatastation.service.actuator;
+
+import org.springframework.beans.factory.annotation.Value;
+import org.springframework.boot.actuate.info.Info;
+import org.springframework.boot.actuate.info.InfoContributor;
+import org.springframework.stereotype.Component;
+
+import static java.lang.String.format;
+
+@Component
+public class AppInfoContributor implements InfoContributor {
+
+ @Value("${git.branch}")
+ private String branch;
+
+ @Value("${git.commit.id.abbrev}")
+ private String commitShort;
+
+ @Value("${git.tags}")
+ private String tag;
+
+ @Value("${build.time}")
+ private String buildTime;
+
+ @Override
+ public void contribute(Info.Builder builder) {
+ builder.withDetail("name", "FAIR Data Station");
+ builder.withDetail("version", getAppVersion());
+ builder.withDetail("builtAt", buildTime);
+ }
+
+ public String getAppVersion() {
+ String version = branch;
+ if (tag != null && !tag.isBlank()) {
+ version = tag;
+ }
+ return format("%s~%s", version, commitShort);
+ }
+
+}
diff --git a/src/main/java/org/fairdatatrain/fairdatastation/service/event/EventAsyncDeliverer.java b/src/main/java/org/fairdatatrain/fairdatastation/service/event/EventAsyncDeliverer.java
new file mode 100644
index 0000000..9382d23
--- /dev/null
+++ b/src/main/java/org/fairdatatrain/fairdatastation/service/event/EventAsyncDeliverer.java
@@ -0,0 +1,63 @@
+/**
+ * The MIT License
+ * Copyright © 2022 FAIR Data Team
+ *
+ * Permission is hereby granted, free of charge, to any person obtaining a copy
+ * of this software and associated documentation files (the "Software"), to deal
+ * in the Software without restriction, including without limitation the rights
+ * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
+ * copies of the Software, and to permit persons to whom the Software is
+ * furnished to do so, subject to the following conditions:
+ *
+ * The above copyright notice and this permission notice shall be included in
+ * all copies or substantial portions of the Software.
+ *
+ * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
+ * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+ * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
+ * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
+ * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
+ * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
+ * THE SOFTWARE.
+ */
+package org.fairdatatrain.fairdatastation.service.event;
+
+import lombok.RequiredArgsConstructor;
+import lombok.extern.slf4j.Slf4j;
+import org.fairdatatrain.fairdatastation.data.model.event.EventDelivery;
+import org.fairdatatrain.fairdatastation.service.event.delivery.EventDeliverer;
+import org.springframework.scheduling.annotation.Scheduled;
+import org.springframework.stereotype.Service;
+import org.springframework.transaction.annotation.Transactional;
+
+import java.util.List;
+
+@Slf4j
+@Service
+@RequiredArgsConstructor
+public class EventAsyncDeliverer {
+
+ private final EventDeliverer eventDeliverer;
+
+ @Transactional
+ @Scheduled(
+ initialDelayString = "${dispatcher.dispatch.initDelay:PT10S}",
+ fixedRateString = "${dispatcher.dispatch.interval:PT30S}"
+ )
+ public void processJobs() {
+ final List eventDeliveryList =
+ eventDeliverer.getNextEventDeliveries();
+ log.info("Delivering {} items in this iteration", eventDeliveryList.size());
+ eventDeliveryList.forEach(this::deliver);
+ }
+
+ protected void deliver(EventDelivery eventDelivery) {
+ log.info("Delivering event delivery {}", eventDelivery.getUuid());
+ if (eventDelivery.getJobEvent() != null) {
+ eventDeliverer.deliver(eventDelivery.getJobEvent(), eventDelivery);
+ }
+ else if (eventDelivery.getJobArtifact() != null) {
+ eventDeliverer.deliver(eventDelivery.getJobArtifact(), eventDelivery);
+ }
+ }
+}
diff --git a/src/main/java/org/fairdatatrain/fairdatastation/service/event/TrainEventService.java b/src/main/java/org/fairdatatrain/fairdatastation/service/event/TrainEventService.java
new file mode 100644
index 0000000..26ea88e
--- /dev/null
+++ b/src/main/java/org/fairdatatrain/fairdatastation/service/event/TrainEventService.java
@@ -0,0 +1,54 @@
+/**
+ * The MIT License
+ * Copyright © 2022 FAIR Data Team
+ *
+ * Permission is hereby granted, free of charge, to any person obtaining a copy
+ * of this software and associated documentation files (the "Software"), to deal
+ * in the Software without restriction, including without limitation the rights
+ * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
+ * copies of the Software, and to permit persons to whom the Software is
+ * furnished to do so, subject to the following conditions:
+ *
+ * The above copyright notice and this permission notice shall be included in
+ * all copies or substantial portions of the Software.
+ *
+ * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
+ * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+ * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
+ * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
+ * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
+ * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
+ * THE SOFTWARE.
+ */
+package org.fairdatatrain.fairdatastation.service.event;
+
+import lombok.RequiredArgsConstructor;
+import lombok.extern.slf4j.Slf4j;
+import org.fairdatatrain.fairdatastation.api.dto.event.train.TrainDispatchPayloadDTO;
+import org.fairdatatrain.fairdatastation.api.dto.event.train.TrainDispatchResponseDTO;
+import org.fairdatatrain.fairdatastation.data.model.enums.JobStatus;
+import org.fairdatatrain.fairdatastation.data.model.event.Job;
+import org.fairdatatrain.fairdatastation.service.event.job.JobService;
+import org.springframework.stereotype.Service;
+import org.springframework.transaction.annotation.Propagation;
+import org.springframework.transaction.annotation.Transactional;
+
+@Slf4j
+@Service
+@RequiredArgsConstructor
+public class TrainEventService {
+
+ private final JobService jobService;
+
+ @Transactional(propagation = Propagation.REQUIRED)
+ public TrainDispatchResponseDTO acceptTrain(TrainDispatchPayloadDTO reqDto) {
+ // TODO: validate before creating a job
+ final Job job = jobService.createJobForTrain(reqDto);
+ return TrainDispatchResponseDTO
+ .builder()
+ .id(job.getUuid().toString())
+ .message("Train queued for processing...")
+ .status(JobStatus.QUEUED)
+ .build();
+ }
+}
diff --git a/src/main/java/org/fairdatatrain/fairdatastation/service/event/delivery/EventDeliverer.java b/src/main/java/org/fairdatatrain/fairdatastation/service/event/delivery/EventDeliverer.java
new file mode 100644
index 0000000..c0067fb
--- /dev/null
+++ b/src/main/java/org/fairdatatrain/fairdatastation/service/event/delivery/EventDeliverer.java
@@ -0,0 +1,117 @@
+/**
+ * The MIT License
+ * Copyright © 2022 FAIR Data Team
+ *
+ * Permission is hereby granted, free of charge, to any person obtaining a copy
+ * of this software and associated documentation files (the "Software"), to deal
+ * in the Software without restriction, including without limitation the rights
+ * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
+ * copies of the Software, and to permit persons to whom the Software is
+ * furnished to do so, subject to the following conditions:
+ *
+ * The above copyright notice and this permission notice shall be included in
+ * all copies or substantial portions of the Software.
+ *
+ * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
+ * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+ * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
+ * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
+ * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
+ * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
+ * THE SOFTWARE.
+ */
+package org.fairdatatrain.fairdatastation.service.event.delivery;
+
+import lombok.RequiredArgsConstructor;
+import lombok.extern.slf4j.Slf4j;
+import org.fairdatatrain.fairdatastation.api.dto.event.job.artifact.JobArtifactDispatchDTO;
+import org.fairdatatrain.fairdatastation.api.dto.event.job.event.JobEventDispatchDTO;
+import org.fairdatatrain.fairdatastation.data.model.event.EventDelivery;
+import org.fairdatatrain.fairdatastation.data.model.event.JobArtifact;
+import org.fairdatatrain.fairdatastation.data.model.event.JobEvent;
+import org.fairdatatrain.fairdatastation.service.event.job.artifact.JobArtifactService;
+import org.fairdatatrain.fairdatastation.service.event.job.event.JobEventService;
+import org.springframework.http.MediaType;
+import org.springframework.stereotype.Service;
+import org.springframework.transaction.annotation.Propagation;
+import org.springframework.transaction.annotation.Transactional;
+import org.springframework.web.reactive.function.client.WebClient;
+import org.springframework.web.reactive.function.client.WebClientException;
+
+import java.sql.Timestamp;
+import java.util.List;
+
+import static java.lang.String.format;
+import static org.fairdatatrain.fairdatastation.utils.TimeUtils.now;
+
+@Slf4j
+@Service
+@RequiredArgsConstructor
+public class EventDeliverer {
+
+ private final EventDeliveryService eventDeliveryService;
+
+ private final JobArtifactService jobArtifactService;
+
+ private final JobEventService jobEventService;
+
+ private final WebClient webClient;
+
+ @Transactional(propagation = Propagation.REQUIRES_NEW)
+ public void deliver(JobArtifact jobArtifact, EventDelivery eventDelivery) {
+ log.debug("Delivering job artifact {}", jobArtifact.getUuid());
+ final JobArtifactDispatchDTO dto = jobArtifactService
+ .getMapper()
+ .toDispatchDTO(jobArtifact);
+ deliver(eventDelivery, jobArtifact.getJob().getCallbackArtifact(), dto);
+ }
+
+ @Transactional(propagation = Propagation.REQUIRES_NEW)
+ public void deliver(JobEvent jobEvent, EventDelivery eventDelivery) {
+ log.debug("Delivering job event {}", jobEvent.getUuid());
+ final JobEventDispatchDTO dto = jobEventService
+ .getMapper()
+ .toDispatchDTO(jobEvent);
+ deliver(eventDelivery, jobEvent.getJob().getCallbackEvent(), dto);
+ }
+
+ protected void deliver(EventDelivery eventDelivery, String uri, Object payload) {
+ final Timestamp dispatchedAt = now();
+ try {
+ dispatch(uri, payload);
+ eventDeliveryService.updateSuccess(eventDelivery, dispatchedAt);
+ }
+ catch (Exception exception) {
+ log.debug("Exception while dispatching artifact", exception);
+ log.warn("Failed to dispatch artifact: {}", exception.getMessage());
+ eventDeliveryService.updateFailed(eventDelivery, dispatchedAt);
+ eventDeliveryService.createNextDelivery(eventDelivery);
+ }
+ }
+
+ private void dispatch(String uri, Object payload) {
+ log.debug("Dispatching payload to {}", uri);
+ try {
+ webClient
+ .post()
+ .uri(uri)
+ .contentType(MediaType.APPLICATION_JSON)
+ .bodyValue(payload)
+ .retrieve()
+ .bodyToMono(String.class)
+ .block();
+ }
+ catch (WebClientException exception) {
+ log.warn(format(
+ "Dispatching event failed: %s", exception.getMessage()
+ ));
+ throw new RuntimeException(
+ "Station responded with status: " + exception.getMessage()
+ );
+ }
+ }
+
+ public List getNextEventDeliveries() {
+ return eventDeliveryService.getNextEventDeliveries();
+ }
+}
diff --git a/src/main/java/org/fairdatatrain/fairdatastation/service/event/delivery/EventDeliveryService.java b/src/main/java/org/fairdatatrain/fairdatastation/service/event/delivery/EventDeliveryService.java
new file mode 100644
index 0000000..b420af1
--- /dev/null
+++ b/src/main/java/org/fairdatatrain/fairdatastation/service/event/delivery/EventDeliveryService.java
@@ -0,0 +1,138 @@
+/**
+ * The MIT License
+ * Copyright © 2022 FAIR Data Team
+ *
+ * Permission is hereby granted, free of charge, to any person obtaining a copy
+ * of this software and associated documentation files (the "Software"), to deal
+ * in the Software without restriction, including without limitation the rights
+ * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
+ * copies of the Software, and to permit persons to whom the Software is
+ * furnished to do so, subject to the following conditions:
+ *
+ * The above copyright notice and this permission notice shall be included in
+ * all copies or substantial portions of the Software.
+ *
+ * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
+ * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+ * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
+ * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
+ * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
+ * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
+ * THE SOFTWARE.
+ */
+package org.fairdatatrain.fairdatastation.service.event.delivery;
+
+import lombok.RequiredArgsConstructor;
+import lombok.extern.slf4j.Slf4j;
+import org.fairdatatrain.fairdatastation.data.model.event.EventDelivery;
+import org.fairdatatrain.fairdatastation.data.model.event.JobArtifact;
+import org.fairdatatrain.fairdatastation.data.model.event.JobEvent;
+import org.fairdatatrain.fairdatastation.data.repository.event.EventDeliveryRepository;
+import org.springframework.stereotype.Service;
+
+import java.sql.Timestamp;
+import java.time.Duration;
+import java.time.Instant;
+import java.util.List;
+import java.util.UUID;
+
+import static org.fairdatatrain.fairdatastation.utils.TimeUtils.now;
+
+@Slf4j
+@Service
+@RequiredArgsConstructor
+public class EventDeliveryService {
+
+ private static final int MAX_RETRIES = 5;
+
+ private final EventDeliveryRepository eventDeliveryRepository;
+
+ public void createInitialDelivery(JobEvent jobEvent) {
+ final EventDelivery eventDelivery = eventDeliveryRepository.saveAndFlush(
+ prepareInitialDelivery()
+ .toBuilder()
+ .jobEvent(jobEvent)
+ .build());
+ log.info("Created initial event delivery {} for job event {}",
+ eventDelivery.getUuid(), jobEvent.getUuid());
+ }
+
+ public void createInitialDelivery(JobArtifact jobArtifact) {
+ final EventDelivery eventDelivery = eventDeliveryRepository.saveAndFlush(
+ prepareInitialDelivery()
+ .toBuilder()
+ .jobArtifact(jobArtifact)
+ .build());
+ log.info("Created initial event delivery {} for job artifact {}",
+ eventDelivery.getUuid(), jobArtifact.getUuid());
+ }
+
+ public void updateSuccess(EventDelivery eventDelivery, Timestamp dispatchedAt) {
+ final EventDelivery updatedDelivery = eventDelivery
+ .toBuilder()
+ .dispatchedAt(dispatchedAt)
+ .delivered(true)
+ .updatedAt(now())
+ .build();
+ eventDeliveryRepository.saveAndFlush(updatedDelivery);
+ }
+
+ public void updateFailed(EventDelivery eventDelivery, Timestamp dispatchedAt) {
+ final EventDelivery updatedDelivery = eventDelivery
+ .toBuilder()
+ .dispatchedAt(dispatchedAt)
+ .delivered(false)
+ .updatedAt(now())
+ .build();
+ eventDeliveryRepository.saveAndFlush(updatedDelivery);
+ }
+
+ public void createNextDelivery(EventDelivery eventDelivery) {
+ final Timestamp now = now();
+ final int retry = eventDelivery.getRetryNumber() + 1;
+ if (retry > MAX_RETRIES) {
+ log.info("Event delivery reached maximal retried ({})",
+ eventDelivery.getUuid());
+ return;
+ }
+ final long minutesNext = (long) Math.pow(2, retry);
+ final Instant nextAt = Instant.now().plus(Duration.ofMinutes(minutesNext));
+ final EventDelivery nextDelivery = EventDelivery
+ .builder()
+ .uuid(UUID.randomUUID())
+ .retryNumber(retry)
+ .priority(0)
+ .delivered(false)
+ .message("")
+ .dispatchAt(Timestamp.from(nextAt))
+ .dispatchedAt(null)
+ .jobEvent(eventDelivery.getJobEvent())
+ .jobArtifact(eventDelivery.getJobArtifact())
+ .createdAt(now)
+ .updatedAt(now)
+ .build();
+ eventDeliveryRepository.saveAndFlush(nextDelivery);
+ }
+
+ private EventDelivery prepareInitialDelivery() {
+ final Timestamp now = now();
+ return EventDelivery
+ .builder()
+ .uuid(UUID.randomUUID())
+ .retryNumber(0)
+ .priority(0)
+ .delivered(false)
+ .message("")
+ .dispatchAt(now)
+ .dispatchedAt(null)
+ .createdAt(now)
+ .updatedAt(now)
+ .build();
+ }
+
+ public List getNextEventDeliveries() {
+ final Timestamp now = now();
+ return eventDeliveryRepository.getNextEventDeliveries(now);
+ }
+
+}
diff --git a/src/main/java/org/fairdatatrain/fairdatastation/service/event/job/JobMapper.java b/src/main/java/org/fairdatatrain/fairdatastation/service/event/job/JobMapper.java
new file mode 100644
index 0000000..64f634c
--- /dev/null
+++ b/src/main/java/org/fairdatatrain/fairdatastation/service/event/job/JobMapper.java
@@ -0,0 +1,138 @@
+/**
+ * The MIT License
+ * Copyright © 2022 FAIR Data Team
+ *
+ * Permission is hereby granted, free of charge, to any person obtaining a copy
+ * of this software and associated documentation files (the "Software"), to deal
+ * in the Software without restriction, including without limitation the rights
+ * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
+ * copies of the Software, and to permit persons to whom the Software is
+ * furnished to do so, subject to the following conditions:
+ *
+ * The above copyright notice and this permission notice shall be included in
+ * all copies or substantial portions of the Software.
+ *
+ * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
+ * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+ * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
+ * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
+ * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
+ * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
+ * THE SOFTWARE.
+ */
+package org.fairdatatrain.fairdatastation.service.event.job;
+
+import lombok.RequiredArgsConstructor;
+import org.fairdatatrain.fairdatastation.api.dto.event.job.JobDTO;
+import org.fairdatatrain.fairdatastation.api.dto.event.job.JobSimpleDTO;
+import org.fairdatatrain.fairdatastation.api.dto.event.train.TrainDispatchPayloadDTO;
+import org.fairdatatrain.fairdatastation.data.model.enums.JobStatus;
+import org.fairdatatrain.fairdatastation.data.model.event.Job;
+import org.fairdatatrain.fairdatastation.service.event.job.artifact.JobArtifactMapper;
+import org.fairdatatrain.fairdatastation.service.event.job.event.JobEventMapper;
+import org.springframework.stereotype.Component;
+
+import java.sql.Timestamp;
+import java.util.Optional;
+
+import static org.fairdatatrain.fairdatastation.utils.TimeUtils.now;
+
+@Component
+@RequiredArgsConstructor
+public class JobMapper {
+
+ private final JobEventMapper jobEventMapper;
+
+ private final JobArtifactMapper jobArtifactMapper;
+
+ public JobSimpleDTO toSimpleDTO(Job job) {
+ return JobSimpleDTO
+ .builder()
+ .uuid(job.getUuid())
+ .remoteId(job.getRemoteId())
+ .status(job.getStatus())
+ .startedAt(
+ Optional.ofNullable(job.getStartedAt())
+ .map(Timestamp::toInstant)
+ .orElse(null)
+ )
+ .finishedAt(
+ Optional.ofNullable(job.getFinishedAt())
+ .map(Timestamp::toInstant)
+ .orElse(null)
+ )
+ .artifacts(
+ job.getArtifacts()
+ .stream()
+ .map(jobArtifactMapper::toDTO)
+ .toList()
+ )
+ .createdAt(job.getCreatedAt().toInstant())
+ .updatedAt(job.getUpdatedAt().toInstant())
+ .version(job.getVersion())
+ .build();
+ }
+
+ public JobDTO toDTO(Job job) {
+ return JobDTO
+ .builder()
+ .uuid(job.getUuid())
+ .remoteId(job.getRemoteId())
+ .status(job.getStatus())
+ .startedAt(
+ Optional.ofNullable(job.getStartedAt())
+ .map(Timestamp::toInstant)
+ .orElse(null)
+ )
+ .finishedAt(
+ Optional.ofNullable(job.getFinishedAt())
+ .map(Timestamp::toInstant)
+ .orElse(null)
+ )
+ .events(
+ job.getEvents()
+ .stream()
+ .map(jobEventMapper::toDTO)
+ .toList()
+ )
+ .artifacts(
+ job.getArtifacts()
+ .stream()
+ .map(jobArtifactMapper::toDTO)
+ .toList()
+ )
+ .createdAt(job.getCreatedAt().toInstant())
+ .updatedAt(job.getUpdatedAt().toInstant())
+ .version(job.getVersion())
+ .build();
+ }
+
+ public Job fromTrainDispatchPayloadDTO(TrainDispatchPayloadDTO reqDto) {
+ final Timestamp now = now();
+ return Job
+ .builder()
+ .secret(reqDto.getSecret())
+ .remoteId(reqDto.getJobUuid())
+ .status(JobStatus.QUEUED)
+ .startedAt(null)
+ .finishedAt(null)
+ .callbackEvent(reqDto.getCallbackEventLocation())
+ .callbackArtifact(reqDto.getCallbackArtifactLocation())
+ .trainUri(reqDto.getTrainUri())
+ .version(0L)
+ .createdAt(now)
+ .updatedAt(now)
+ .build();
+ }
+
+ public Job updateStatus(Job job, JobStatus status) {
+ final Timestamp now = now();
+ job.setStatus(status);
+ job.setUpdatedAt(now);
+ if (status.equals(JobStatus.FAILED) || status.equals(JobStatus.FINISHED)) {
+ // TODO: list "terminal" states
+ job.setFinishedAt(now);
+ }
+ return job;
+ }
+}
diff --git a/src/main/java/org/fairdatatrain/fairdatastation/service/event/job/JobService.java b/src/main/java/org/fairdatatrain/fairdatastation/service/event/job/JobService.java
new file mode 100644
index 0000000..84a6f5a
--- /dev/null
+++ b/src/main/java/org/fairdatatrain/fairdatastation/service/event/job/JobService.java
@@ -0,0 +1,86 @@
+/**
+ * The MIT License
+ * Copyright © 2022 FAIR Data Team
+ *
+ * Permission is hereby granted, free of charge, to any person obtaining a copy
+ * of this software and associated documentation files (the "Software"), to deal
+ * in the Software without restriction, including without limitation the rights
+ * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
+ * copies of the Software, and to permit persons to whom the Software is
+ * furnished to do so, subject to the following conditions:
+ *
+ * The above copyright notice and this permission notice shall be included in
+ * all copies or substantial portions of the Software.
+ *
+ * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
+ * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+ * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
+ * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
+ * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
+ * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
+ * THE SOFTWARE.
+ */
+package org.fairdatatrain.fairdatastation.service.event.job;
+
+import lombok.RequiredArgsConstructor;
+import lombok.extern.slf4j.Slf4j;
+import org.fairdatatrain.fairdatastation.api.dto.event.job.JobDTO;
+import org.fairdatatrain.fairdatastation.api.dto.event.job.JobSimpleDTO;
+import org.fairdatatrain.fairdatastation.api.dto.event.train.TrainDispatchPayloadDTO;
+import org.fairdatatrain.fairdatastation.data.model.enums.JobStatus;
+import org.fairdatatrain.fairdatastation.data.model.event.Job;
+import org.fairdatatrain.fairdatastation.data.repository.event.JobRepository;
+import org.fairdatatrain.fairdatastation.exception.NotFoundException;
+import org.springframework.data.domain.Page;
+import org.springframework.data.domain.Pageable;
+import org.springframework.stereotype.Service;
+import org.springframework.transaction.annotation.Propagation;
+import org.springframework.transaction.annotation.Transactional;
+
+import java.util.Optional;
+import java.util.UUID;
+
+@Service
+@RequiredArgsConstructor
+@Slf4j
+public class JobService {
+
+ private static final String ENTITY_NAME = "Job";
+
+ private final JobRepository jobRepository;
+
+ private final JobMapper jobMapper;
+
+ @Transactional(propagation = Propagation.REQUIRED, readOnly = true)
+ public Page getJobs(Pageable pageable) {
+ return jobRepository
+ .findAll(pageable)
+ .map(jobMapper::toSimpleDTO);
+ }
+
+ public Job getByIdOrThrow(UUID jobUuid) throws NotFoundException {
+ return jobRepository
+ .findById(jobUuid)
+ .orElseThrow(() -> new NotFoundException(ENTITY_NAME, jobUuid));
+ }
+
+ @Transactional(propagation = Propagation.REQUIRED, readOnly = true)
+ public JobDTO getJob(UUID jobUuid) throws NotFoundException {
+ return jobMapper.toDTO(getByIdOrThrow(jobUuid));
+ }
+
+ @Transactional(propagation = Propagation.REQUIRED)
+ public Job createJobForTrain(TrainDispatchPayloadDTO reqDto) {
+ final Job job = jobMapper.fromTrainDispatchPayloadDTO(reqDto);
+ return jobRepository.saveAndFlush(job);
+ }
+
+ public Optional getNextJob() {
+ // TODO: priority?
+ return jobRepository.findFirstByFinishedAtIsNullOrderByCreatedAtAsc();
+ }
+
+ public void updateStatus(Job job, JobStatus status) {
+ jobRepository.saveAndFlush(jobMapper.updateStatus(job, status));
+ }
+}
diff --git a/src/main/java/org/fairdatatrain/fairdatastation/service/event/job/artifact/JobArtifactMapper.java b/src/main/java/org/fairdatatrain/fairdatastation/service/event/job/artifact/JobArtifactMapper.java
new file mode 100644
index 0000000..601b642
--- /dev/null
+++ b/src/main/java/org/fairdatatrain/fairdatastation/service/event/job/artifact/JobArtifactMapper.java
@@ -0,0 +1,88 @@
+/**
+ * The MIT License
+ * Copyright © 2022 FAIR Data Team
+ *
+ * Permission is hereby granted, free of charge, to any person obtaining a copy
+ * of this software and associated documentation files (the "Software"), to deal
+ * in the Software without restriction, including without limitation the rights
+ * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
+ * copies of the Software, and to permit persons to whom the Software is
+ * furnished to do so, subject to the following conditions:
+ *
+ * The above copyright notice and this permission notice shall be included in
+ * all copies or substantial portions of the Software.
+ *
+ * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
+ * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+ * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
+ * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
+ * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
+ * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
+ * THE SOFTWARE.
+ */
+package org.fairdatatrain.fairdatastation.service.event.job.artifact;
+
+import org.fairdatatrain.fairdatastation.api.dto.event.job.artifact.JobArtifactDTO;
+import org.fairdatatrain.fairdatastation.api.dto.event.job.artifact.JobArtifactDispatchDTO;
+import org.fairdatatrain.fairdatastation.data.model.enums.ArtifactStorage;
+import org.fairdatatrain.fairdatastation.data.model.event.Job;
+import org.fairdatatrain.fairdatastation.data.model.event.JobArtifact;
+import org.springframework.stereotype.Component;
+
+import java.sql.Timestamp;
+import java.util.Base64;
+
+import static org.fairdatatrain.fairdatastation.utils.TimeUtils.now;
+
+@Component
+public class JobArtifactMapper {
+
+ public JobArtifactDTO toDTO(JobArtifact jobArtifact) {
+ return JobArtifactDTO
+ .builder()
+ .uuid(jobArtifact.getUuid())
+ .displayName(jobArtifact.getDisplayName())
+ .filename(jobArtifact.getFilename())
+ .bytesize(jobArtifact.getBytesize())
+ .contentType(jobArtifact.getContentType())
+ .hash(jobArtifact.getHash())
+ .occurredAt(jobArtifact.getOccurredAt().toInstant())
+ .createdAt(jobArtifact.getCreatedAt().toInstant())
+ .updatedAt(jobArtifact.getUpdatedAt().toInstant())
+ .build();
+ }
+
+ public JobArtifact create(Job job, String displayName, String filename, String contentType,
+ byte[] data, String hash) {
+ final Timestamp now = now();
+ return JobArtifact
+ .builder()
+ .job(job)
+ .displayName(displayName)
+ .filename(filename)
+ .contentType(contentType)
+ .data(data)
+ .storage(ArtifactStorage.POSTGRES)
+ .bytesize((long) data.length)
+ .hash(hash)
+ .occurredAt(now)
+ .createdAt(now)
+ .updatedAt(now)
+ .build();
+ }
+
+ public JobArtifactDispatchDTO toDispatchDTO(JobArtifact artifact) {
+ return JobArtifactDispatchDTO
+ .builder()
+ .remoteId(artifact.getJob().getUuid().toString())
+ .secret(artifact.getJob().getSecret())
+ .displayName(artifact.getDisplayName())
+ .filename(artifact.getFilename())
+ .bytesize(artifact.getBytesize())
+ .hash(artifact.getHash())
+ .contentType(artifact.getContentType())
+ .base64data(Base64.getEncoder().encodeToString(artifact.getData()))
+ .occurredAt(artifact.getOccurredAt().toInstant())
+ .build();
+ }
+}
diff --git a/src/main/java/org/fairdatatrain/fairdatastation/service/event/job/artifact/JobArtifactService.java b/src/main/java/org/fairdatatrain/fairdatastation/service/event/job/artifact/JobArtifactService.java
new file mode 100644
index 0000000..c909bd1
--- /dev/null
+++ b/src/main/java/org/fairdatatrain/fairdatastation/service/event/job/artifact/JobArtifactService.java
@@ -0,0 +1,120 @@
+/**
+ * The MIT License
+ * Copyright © 2022 FAIR Data Team
+ *
+ * Permission is hereby granted, free of charge, to any person obtaining a copy
+ * of this software and associated documentation files (the "Software"), to deal
+ * in the Software without restriction, including without limitation the rights
+ * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
+ * copies of the Software, and to permit persons to whom the Software is
+ * furnished to do so, subject to the following conditions:
+ *
+ * The above copyright notice and this permission notice shall be included in
+ * all copies or substantial portions of the Software.
+ *
+ * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
+ * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+ * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
+ * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
+ * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
+ * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
+ * THE SOFTWARE.
+ */
+package org.fairdatatrain.fairdatastation.service.event.job.artifact;
+
+import lombok.RequiredArgsConstructor;
+import lombok.extern.slf4j.Slf4j;
+import org.fairdatatrain.fairdatastation.api.dto.event.job.artifact.JobArtifactDTO;
+import org.fairdatatrain.fairdatastation.data.model.enums.ArtifactStorage;
+import org.fairdatatrain.fairdatastation.data.model.event.Job;
+import org.fairdatatrain.fairdatastation.data.model.event.JobArtifact;
+import org.fairdatatrain.fairdatastation.data.repository.event.JobArtifactRepository;
+import org.fairdatatrain.fairdatastation.exception.NotFoundException;
+import org.fairdatatrain.fairdatastation.service.event.delivery.EventDeliveryService;
+import org.fairdatatrain.fairdatastation.service.event.job.JobService;
+import org.springframework.stereotype.Service;
+import org.springframework.transaction.annotation.Propagation;
+import org.springframework.transaction.annotation.Transactional;
+
+import java.security.MessageDigest;
+import java.security.NoSuchAlgorithmException;
+import java.util.Arrays;
+import java.util.List;
+import java.util.Map;
+import java.util.UUID;
+
+import static java.lang.String.format;
+import static org.fairdatatrain.fairdatastation.utils.HashUtils.bytesToHex;
+
+@Slf4j
+@Service
+@RequiredArgsConstructor
+public class JobArtifactService {
+
+ private static final String ENTITY_NAME = "JobArtifact";
+
+ private final JobArtifactRepository jobArtifactRepository;
+
+ private final JobArtifactMapper jobArtifactMapper;
+
+ private final JobService jobService;
+
+ private final EventDeliveryService eventDeliveryService;
+
+ @Transactional(propagation = Propagation.REQUIRED, readOnly = true)
+ public List getArtifactsForJob(UUID jobUuid) throws NotFoundException {
+ final Job job = jobService.getByIdOrThrow(jobUuid);
+ return job
+ .getArtifacts()
+ .stream()
+ .map(jobArtifactMapper::toDTO)
+ .toList();
+ }
+
+ public JobArtifact getByIdOrThrow(UUID jobUuid, UUID artifactUuid) throws NotFoundException {
+ final JobArtifact jobArtifact = jobArtifactRepository
+ .findById(artifactUuid)
+ .orElseThrow(() -> new NotFoundException(ENTITY_NAME, artifactUuid));
+ if (!jobArtifact.getJob().getUuid().equals(jobUuid)) {
+ throw new NotFoundException(ENTITY_NAME, Map.of("jobUuid", jobUuid.toString()));
+ }
+ return jobArtifact;
+ }
+
+ @Transactional(propagation = Propagation.REQUIRED, readOnly = true)
+ public byte[] getArtifactData(JobArtifact artifact) {
+ if (artifact.getStorage().equals(ArtifactStorage.POSTGRES)) {
+ return artifact.getData();
+ }
+ throw new RuntimeException(
+ format("Unsupported artifact storage: %s", artifact.getStorage())
+ );
+ }
+
+ @Transactional
+ public void createArtifact(Job job, String displayName, String filename,
+ String contentType, byte[] data) {
+ System.out.println(Arrays.toString(data));
+ final String hash = computeHash(data);
+ final JobArtifact jobArtifact = jobArtifactRepository.saveAndFlush(
+ jobArtifactMapper.create(job, displayName, filename, contentType, data, hash)
+ );
+ eventDeliveryService.createInitialDelivery(jobArtifact);
+ log.info("Created artifact {} for job {}", jobArtifact.getUuid(), job.getUuid());
+ }
+
+ private String computeHash(byte[] data) {
+ final MessageDigest digest;
+ try {
+ digest = MessageDigest.getInstance("SHA-256");
+ }
+ catch (NoSuchAlgorithmException exception) {
+ throw new RuntimeException("SHA-256 hashing is not supported");
+ }
+ return bytesToHex(digest.digest(data));
+ }
+
+ public JobArtifactMapper getMapper() {
+ return jobArtifactMapper;
+ }
+}
diff --git a/src/main/java/org/fairdatatrain/fairdatastation/service/event/job/event/JobEventMapper.java b/src/main/java/org/fairdatatrain/fairdatastation/service/event/job/event/JobEventMapper.java
new file mode 100644
index 0000000..e142e63
--- /dev/null
+++ b/src/main/java/org/fairdatatrain/fairdatastation/service/event/job/event/JobEventMapper.java
@@ -0,0 +1,74 @@
+/**
+ * The MIT License
+ * Copyright © 2022 FAIR Data Team
+ *
+ * Permission is hereby granted, free of charge, to any person obtaining a copy
+ * of this software and associated documentation files (the "Software"), to deal
+ * in the Software without restriction, including without limitation the rights
+ * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
+ * copies of the Software, and to permit persons to whom the Software is
+ * furnished to do so, subject to the following conditions:
+ *
+ * The above copyright notice and this permission notice shall be included in
+ * all copies or substantial portions of the Software.
+ *
+ * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
+ * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+ * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
+ * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
+ * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
+ * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
+ * THE SOFTWARE.
+ */
+package org.fairdatatrain.fairdatastation.service.event.job.event;
+
+import org.fairdatatrain.fairdatastation.api.dto.event.job.event.JobEventDTO;
+import org.fairdatatrain.fairdatastation.api.dto.event.job.event.JobEventDispatchDTO;
+import org.fairdatatrain.fairdatastation.data.model.enums.JobStatus;
+import org.fairdatatrain.fairdatastation.data.model.event.Job;
+import org.fairdatatrain.fairdatastation.data.model.event.JobEvent;
+import org.springframework.stereotype.Component;
+
+import java.sql.Timestamp;
+
+import static org.fairdatatrain.fairdatastation.utils.TimeUtils.now;
+
+@Component
+public class JobEventMapper {
+
+ public JobEventDTO toDTO(JobEvent jobEvent) {
+ return JobEventDTO
+ .builder()
+ .uuid(jobEvent.getUuid())
+ .resultStatus(jobEvent.getResultStatus())
+ .message(jobEvent.getMessage())
+ .occurredAt(jobEvent.getOccurredAt().toInstant())
+ .createdAt(jobEvent.getCreatedAt().toInstant())
+ .updatedAt(jobEvent.getUpdatedAt().toInstant())
+ .build();
+ }
+
+ public JobEvent create(Job job, String message, JobStatus status) {
+ final Timestamp now = now();
+ return JobEvent
+ .builder()
+ .job(job)
+ .message(message)
+ .resultStatus(status)
+ .occurredAt(now)
+ .createdAt(now)
+ .updatedAt(now)
+ .build();
+ }
+
+ public JobEventDispatchDTO toDispatchDTO(JobEvent event) {
+ return JobEventDispatchDTO
+ .builder()
+ .remoteId(event.getJob().getUuid().toString())
+ .secret(event.getJob().getSecret())
+ .message(event.getMessage())
+ .resultStatus(event.getResultStatus())
+ .occurredAt(event.getOccurredAt().toInstant())
+ .build();
+ }
+}
diff --git a/src/main/java/org/fairdatatrain/fairdatastation/service/event/job/event/JobEventService.java b/src/main/java/org/fairdatatrain/fairdatastation/service/event/job/event/JobEventService.java
new file mode 100644
index 0000000..16e3003
--- /dev/null
+++ b/src/main/java/org/fairdatatrain/fairdatastation/service/event/job/event/JobEventService.java
@@ -0,0 +1,81 @@
+/**
+ * The MIT License
+ * Copyright © 2022 FAIR Data Team
+ *
+ * Permission is hereby granted, free of charge, to any person obtaining a copy
+ * of this software and associated documentation files (the "Software"), to deal
+ * in the Software without restriction, including without limitation the rights
+ * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
+ * copies of the Software, and to permit persons to whom the Software is
+ * furnished to do so, subject to the following conditions:
+ *
+ * The above copyright notice and this permission notice shall be included in
+ * all copies or substantial portions of the Software.
+ *
+ * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
+ * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+ * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
+ * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
+ * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
+ * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
+ * THE SOFTWARE.
+ */
+package org.fairdatatrain.fairdatastation.service.event.job.event;
+
+import lombok.RequiredArgsConstructor;
+import lombok.extern.slf4j.Slf4j;
+import org.fairdatatrain.fairdatastation.api.dto.event.job.event.JobEventDTO;
+import org.fairdatatrain.fairdatastation.data.model.enums.JobStatus;
+import org.fairdatatrain.fairdatastation.data.model.event.Job;
+import org.fairdatatrain.fairdatastation.data.model.event.JobEvent;
+import org.fairdatatrain.fairdatastation.data.repository.event.JobEventRepository;
+import org.fairdatatrain.fairdatastation.exception.NotFoundException;
+import org.fairdatatrain.fairdatastation.service.event.delivery.EventDeliveryService;
+import org.fairdatatrain.fairdatastation.service.event.job.JobService;
+import org.springframework.stereotype.Service;
+import org.springframework.transaction.annotation.Propagation;
+import org.springframework.transaction.annotation.Transactional;
+
+import java.util.List;
+import java.util.UUID;
+
+@Slf4j
+@Service
+@RequiredArgsConstructor
+public class JobEventService {
+
+ private final JobEventRepository jobEventRepository;
+
+ private final JobEventMapper jobEventMapper;
+
+ private final JobService jobService;
+
+ private final EventDeliveryService eventDeliveryService;
+
+ @Transactional(propagation = Propagation.REQUIRED, readOnly = true)
+ public List getEventsForJob(UUID jobUuid) throws NotFoundException {
+ final Job job = jobService.getByIdOrThrow(jobUuid);
+ return job
+ .getEvents()
+ .stream()
+ .map(jobEventMapper::toDTO)
+ .toList();
+ }
+
+ public void createEvent(Job job, String message) {
+ createEvent(job, message, null);
+ }
+
+ @Transactional
+ public void createEvent(Job job, String message, JobStatus status) {
+ final JobEvent jobEvent = jobEventRepository.saveAndFlush(
+ jobEventMapper.create(job, message, status)
+ );
+ eventDeliveryService.createInitialDelivery(jobEvent);
+ log.info("Created event {} for job {}", jobEvent.getUuid(), job.getUuid());
+ }
+
+ public JobEventMapper getMapper() {
+ return jobEventMapper;
+ }
+}
diff --git a/src/main/java/org/fairdatatrain/fairdatastation/service/interaction/GenericTrainInteraction.java b/src/main/java/org/fairdatatrain/fairdatastation/service/interaction/GenericTrainInteraction.java
new file mode 100644
index 0000000..8808d93
--- /dev/null
+++ b/src/main/java/org/fairdatatrain/fairdatastation/service/interaction/GenericTrainInteraction.java
@@ -0,0 +1,114 @@
+/**
+ * The MIT License
+ * Copyright © 2022 FAIR Data Team
+ *
+ * Permission is hereby granted, free of charge, to any person obtaining a copy
+ * of this software and associated documentation files (the "Software"), to deal
+ * in the Software without restriction, including without limitation the rights
+ * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
+ * copies of the Software, and to permit persons to whom the Software is
+ * furnished to do so, subject to the following conditions:
+ *
+ * The above copyright notice and this permission notice shall be included in
+ * all copies or substantial portions of the Software.
+ *
+ * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
+ * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+ * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
+ * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
+ * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
+ * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
+ * THE SOFTWARE.
+ */
+package org.fairdatatrain.fairdatastation.service.interaction;
+
+import lombok.RequiredArgsConstructor;
+import lombok.extern.slf4j.Slf4j;
+import org.eclipse.rdf4j.model.Model;
+import org.eclipse.rdf4j.model.Resource;
+import org.fairdatatrain.fairdatastation.data.model.enums.JobStatus;
+import org.fairdatatrain.fairdatastation.data.model.event.Job;
+import org.fairdatatrain.fairdatastation.service.event.job.JobService;
+import org.fairdatatrain.fairdatastation.service.event.job.event.JobEventService;
+import org.fairdatatrain.fairdatastation.service.interaction.entity.TrainType;
+import org.fairdatatrain.fairdatastation.service.interaction.fetch.TrainFetcher;
+import org.fairdatatrain.fairdatastation.service.interaction.train.ITrainInteraction;
+import org.fairdatatrain.fairdatastation.service.interaction.train.TrainInteractionFactory;
+import org.fairdatatrain.fairdatastation.service.validation.TrainValidationService;
+import org.springframework.stereotype.Service;
+
+import static java.lang.String.format;
+
+@Slf4j
+@Service
+@RequiredArgsConstructor
+public class GenericTrainInteraction {
+
+ private final TrainInteractionFactory trainInteractionFactory;
+
+ private final TrainFetcher trainFetcher;
+
+ private final TrainValidationService trainValidationService;
+
+ private final JobEventService jobEventService;
+
+ private final JobService jobService;
+
+ public void interact(Job job) {
+ sendInfo(job, "Retrieved job from queue", JobStatus.RUNNING);
+ try {
+ sendInfo(job, format("Fetch: Fetching details for train: %s",
+ job.getTrainUri()));
+ final Model trainMetadata = fetchTrainMetadata(job);
+ sendInfo(job, format("Fetch: Details fetched successfully for train: %s",
+ job.getTrainUri()));
+
+ sendInfo(job, "Validation: Validating train metadata and checking type");
+ final Resource train = extractValidTrain(trainMetadata);
+ sendInfo(job, "Validation: Train metadata validated");
+ final TrainType trainType =
+ trainValidationService.determineTrainType(trainMetadata, train);
+ if (trainType == null) {
+ throw new RuntimeException("Validation: Cannot determine train type");
+ }
+ final ITrainInteraction trainInteraction =
+ trainInteractionFactory.getTrainInteractionService(trainType);
+ trainInteraction.interact(job, trainMetadata, train);
+ }
+ catch (Exception exception) {
+ handleInteractionFailed(job, exception.getMessage());
+ }
+ }
+
+ private void handleInteractionFailed(Job job, String message) {
+ jobEventService.createEvent(job, message, JobStatus.FAILED);
+ jobService.updateStatus(job, JobStatus.FAILED);
+ }
+
+ private void sendInfo(Job job, String message) {
+ jobEventService.createEvent(job, message);
+ }
+
+ private void sendInfo(Job job, String message, JobStatus status) {
+ jobEventService.createEvent(job, message, status);
+ }
+
+ private Model fetchTrainMetadata(Job job) {
+ try {
+ return trainFetcher.fetchTrainMetadata(job.getTrainUri());
+ }
+ catch (Exception exception) {
+ throw new RuntimeException("Preparation: Failed to fetch train metadata");
+ }
+ }
+
+ private Resource extractValidTrain(Model trainMetadata) {
+ try {
+ return trainValidationService.validate(trainMetadata);
+ }
+ catch (Exception exception) {
+ throw new RuntimeException(format("Validation: Invalid train (%s)",
+ exception.getMessage()));
+ }
+ }
+}
diff --git a/src/main/java/org/fairdatatrain/fairdatastation/service/interaction/JobProcessor.java b/src/main/java/org/fairdatatrain/fairdatastation/service/interaction/JobProcessor.java
new file mode 100644
index 0000000..169c009
--- /dev/null
+++ b/src/main/java/org/fairdatatrain/fairdatastation/service/interaction/JobProcessor.java
@@ -0,0 +1,70 @@
+/**
+ * The MIT License
+ * Copyright © 2022 FAIR Data Team
+ *
+ * Permission is hereby granted, free of charge, to any person obtaining a copy
+ * of this software and associated documentation files (the "Software"), to deal
+ * in the Software without restriction, including without limitation the rights
+ * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
+ * copies of the Software, and to permit persons to whom the Software is
+ * furnished to do so, subject to the following conditions:
+ *
+ * The above copyright notice and this permission notice shall be included in
+ * all copies or substantial portions of the Software.
+ *
+ * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
+ * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+ * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
+ * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
+ * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
+ * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
+ * THE SOFTWARE.
+ */
+package org.fairdatatrain.fairdatastation.service.interaction;
+
+import lombok.RequiredArgsConstructor;
+import lombok.SneakyThrows;
+import lombok.extern.slf4j.Slf4j;
+import org.fairdatatrain.fairdatastation.data.model.event.Job;
+import org.fairdatatrain.fairdatastation.service.event.job.JobService;
+import org.springframework.scheduling.annotation.Scheduled;
+import org.springframework.stereotype.Component;
+import org.springframework.transaction.annotation.Transactional;
+
+import java.util.Optional;
+
+@Slf4j
+@Component
+@RequiredArgsConstructor
+public class JobProcessor {
+
+ private final JobService jobService;
+
+ private final GenericTrainInteraction trainInteraction;
+
+ // TODO: config
+ // TODO: multi-thread? configurable?
+ @Scheduled(
+ initialDelayString = "${dispatcher.dispatch.initDelay:PT1M}",
+ fixedRateString = "${dispatcher.dispatch.interval:PT1M}"
+ )
+ public void processJobs() {
+ log.info("Starting to process jobs");
+ Optional job = jobService.getNextJob();
+ while (job.isPresent()) {
+ processJob(job.get());
+ job = jobService.getNextJob();
+ }
+ log.info("No more jobs to process now");
+ }
+
+ @SneakyThrows
+ @Transactional
+ public void processJob(Job job) {
+ // TODO: some stages? retries?
+ // interruption on the go (no need to rerun from start)
+ log.info("Processing job {}", job.getUuid());
+ trainInteraction.interact(job);
+ log.info("Processing job {}: done", job.getUuid());
+ }
+}
diff --git a/src/main/java/org/fairdatatrain/fairdatastation/service/interaction/entity/InteractionArtifact.java b/src/main/java/org/fairdatatrain/fairdatastation/service/interaction/entity/InteractionArtifact.java
new file mode 100644
index 0000000..8593985
--- /dev/null
+++ b/src/main/java/org/fairdatatrain/fairdatastation/service/interaction/entity/InteractionArtifact.java
@@ -0,0 +1,41 @@
+/**
+ * The MIT License
+ * Copyright © 2022 FAIR Data Team
+ *
+ * Permission is hereby granted, free of charge, to any person obtaining a copy
+ * of this software and associated documentation files (the "Software"), to deal
+ * in the Software without restriction, including without limitation the rights
+ * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
+ * copies of the Software, and to permit persons to whom the Software is
+ * furnished to do so, subject to the following conditions:
+ *
+ * The above copyright notice and this permission notice shall be included in
+ * all copies or substantial portions of the Software.
+ *
+ * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
+ * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+ * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
+ * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
+ * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
+ * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
+ * THE SOFTWARE.
+ */
+package org.fairdatatrain.fairdatastation.service.interaction.entity;
+
+import lombok.*;
+
+@NoArgsConstructor
+@AllArgsConstructor
+@Getter
+@Setter
+@Builder
+public class InteractionArtifact {
+
+ private String name;
+
+ private String filename;
+
+ private String contentType;
+
+ private byte[] data;
+}
diff --git a/src/main/java/org/fairdatatrain/fairdatastation/service/interaction/entity/TrainType.java b/src/main/java/org/fairdatatrain/fairdatastation/service/interaction/entity/TrainType.java
new file mode 100644
index 0000000..e901d13
--- /dev/null
+++ b/src/main/java/org/fairdatatrain/fairdatastation/service/interaction/entity/TrainType.java
@@ -0,0 +1,29 @@
+/**
+ * The MIT License
+ * Copyright © 2022 FAIR Data Team
+ *
+ * Permission is hereby granted, free of charge, to any person obtaining a copy
+ * of this software and associated documentation files (the "Software"), to deal
+ * in the Software without restriction, including without limitation the rights
+ * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
+ * copies of the Software, and to permit persons to whom the Software is
+ * furnished to do so, subject to the following conditions:
+ *
+ * The above copyright notice and this permission notice shall be included in
+ * all copies or substantial portions of the Software.
+ *
+ * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
+ * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+ * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
+ * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
+ * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
+ * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
+ * THE SOFTWARE.
+ */
+package org.fairdatatrain.fairdatastation.service.interaction.entity;
+
+// Supported train types by FDS
+public enum TrainType {
+ FHIR_TRAIN,
+ SPARQL_TRAIN,
+}
diff --git a/src/main/java/org/fairdatatrain/fairdatastation/service/interaction/fetch/TrainFetcher.java b/src/main/java/org/fairdatatrain/fairdatastation/service/interaction/fetch/TrainFetcher.java
new file mode 100644
index 0000000..e36d333
--- /dev/null
+++ b/src/main/java/org/fairdatatrain/fairdatastation/service/interaction/fetch/TrainFetcher.java
@@ -0,0 +1,115 @@
+/**
+ * The MIT License
+ * Copyright © 2022 FAIR Data Team
+ *
+ * Permission is hereby granted, free of charge, to any person obtaining a copy
+ * of this software and associated documentation files (the "Software"), to deal
+ * in the Software without restriction, including without limitation the rights
+ * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
+ * copies of the Software, and to permit persons to whom the Software is
+ * furnished to do so, subject to the following conditions:
+ *
+ * The above copyright notice and this permission notice shall be included in
+ * all copies or substantial portions of the Software.
+ *
+ * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
+ * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+ * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
+ * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
+ * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
+ * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
+ * THE SOFTWARE.
+ */
+package org.fairdatatrain.fairdatastation.service.interaction.fetch;
+
+import lombok.RequiredArgsConstructor;
+import lombok.SneakyThrows;
+import lombok.extern.slf4j.Slf4j;
+import org.eclipse.rdf4j.model.Model;
+import org.eclipse.rdf4j.rio.RDFFormat;
+import org.springframework.http.HttpStatus;
+import org.springframework.http.MediaType;
+import org.springframework.stereotype.Component;
+import org.springframework.web.client.HttpClientErrorException;
+import org.springframework.web.reactive.function.client.WebClient;
+import org.springframework.web.reactive.function.client.WebClientException;
+
+import java.net.URI;
+
+import static java.lang.String.format;
+import static java.util.Optional.ofNullable;
+import static org.fairdatatrain.fairdatastation.utils.RdfUtils.read;
+
+@Slf4j
+@Component
+@RequiredArgsConstructor
+public class TrainFetcher {
+
+ private static final String MSG_MKRQ = "Making request to '%s'";
+ private static final String MSG_RCV = "Request to '%s' successfully received";
+ private static final String MSG_PARSE = "Request to '%s' successfully parsed";
+ private static final String MSG_FAIL = "Request to '%s' failed";
+ private static final String MSG_ERROR = "HTTP request failed";
+
+ private final WebClient webClient;
+
+ public Model fetchTrainMetadata(String trainUri) {
+ return fetchModel(trainUri);
+ }
+
+ public Model fetchPayloadMetadata(String payloadUri) {
+ return fetchModel(payloadUri);
+ }
+
+ public String fetchSimplePayload(String uri) {
+ return fetchStringData(uri);
+ }
+
+ @SneakyThrows
+ public Model fetchModel(String uri) {
+ log.info(format(MSG_MKRQ, uri));
+ try {
+ final String response = webClient
+ .get()
+ .uri(URI.create(uri))
+ .accept(MediaType.parseMediaType(RDFFormat.TURTLE.getDefaultMIMEType()))
+ .retrieve()
+ .bodyToMono(String.class)
+ .block();
+ log.info(format(MSG_RCV, uri));
+ final Model result = read(response, uri, RDFFormat.TURTLE);
+ log.info(format(MSG_PARSE, uri));
+ return result;
+ }
+ catch (WebClientException exception) {
+ log.info(format(MSG_FAIL, uri));
+ throw new HttpClientErrorException(
+ HttpStatus.INTERNAL_SERVER_ERROR,
+ ofNullable(exception.getMessage()).orElse(MSG_ERROR)
+ );
+ }
+ }
+
+ @SneakyThrows
+ public String fetchStringData(String uri) {
+ log.info(format(MSG_MKRQ, uri));
+ try {
+ final String response = webClient
+ .get()
+ .uri(URI.create(uri))
+ .accept(MediaType.TEXT_PLAIN)
+ .retrieve()
+ .bodyToMono(String.class)
+ .block();
+ log.info(format(MSG_RCV, uri));
+ return response;
+ }
+ catch (WebClientException exception) {
+ log.info(format(MSG_FAIL, uri));
+ throw new HttpClientErrorException(
+ HttpStatus.INTERNAL_SERVER_ERROR,
+ ofNullable(exception.getMessage()).orElse(MSG_ERROR)
+ );
+ }
+ }
+}
diff --git a/src/main/java/org/fairdatatrain/fairdatastation/service/interaction/train/AbstractTrainInteraction.java b/src/main/java/org/fairdatatrain/fairdatastation/service/interaction/train/AbstractTrainInteraction.java
new file mode 100644
index 0000000..4d0b158
--- /dev/null
+++ b/src/main/java/org/fairdatatrain/fairdatastation/service/interaction/train/AbstractTrainInteraction.java
@@ -0,0 +1,138 @@
+/**
+ * The MIT License
+ * Copyright © 2022 FAIR Data Team
+ *
+ * Permission is hereby granted, free of charge, to any person obtaining a copy
+ * of this software and associated documentation files (the "Software"), to deal
+ * in the Software without restriction, including without limitation the rights
+ * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
+ * copies of the Software, and to permit persons to whom the Software is
+ * furnished to do so, subject to the following conditions:
+ *
+ * The above copyright notice and this permission notice shall be included in
+ * all copies or substantial portions of the Software.
+ *
+ * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
+ * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+ * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
+ * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
+ * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
+ * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
+ * THE SOFTWARE.
+ */
+package org.fairdatatrain.fairdatastation.service.interaction.train;
+
+import lombok.RequiredArgsConstructor;
+import lombok.extern.slf4j.Slf4j;
+import nl.dtls.fairdatapoint.vocabulary.FDT;
+import org.eclipse.rdf4j.model.Model;
+import org.eclipse.rdf4j.model.Resource;
+import org.eclipse.rdf4j.model.Value;
+import org.fairdatatrain.fairdatastation.data.model.enums.JobStatus;
+import org.fairdatatrain.fairdatastation.data.model.event.Job;
+import org.fairdatatrain.fairdatastation.service.accesscontrol.BasicAccessControlService;
+import org.fairdatatrain.fairdatastation.service.event.job.JobService;
+import org.fairdatatrain.fairdatastation.service.event.job.artifact.JobArtifactService;
+import org.fairdatatrain.fairdatastation.service.event.job.event.JobEventService;
+import org.fairdatatrain.fairdatastation.service.interaction.entity.InteractionArtifact;
+import org.fairdatatrain.fairdatastation.service.interaction.fetch.TrainFetcher;
+
+import static java.lang.String.format;
+import static org.fairdatatrain.fairdatastation.utils.RdfUtils.getObjectBy;
+import static org.fairdatatrain.fairdatastation.utils.RdfUtils.getStringObjectBy;
+
+@Slf4j
+@RequiredArgsConstructor
+public abstract class AbstractTrainInteraction {
+
+ private final BasicAccessControlService accessControlService;
+
+ private final JobEventService jobEventService;
+
+ private final JobArtifactService jobArtifactService;
+
+ private final JobService jobService;
+
+ private final TrainFetcher trainFetcher;
+
+ protected BasicAccessControlService getAccessControlService() {
+ return accessControlService;
+ }
+
+ protected JobEventService getJobEventService() {
+ return jobEventService;
+ }
+
+ protected JobArtifactService getJobArtifactService() {
+ return jobArtifactService;
+ }
+
+ protected JobService getJobService() {
+ return jobService;
+ }
+
+ protected TrainFetcher getTrainFetcher() {
+ return trainFetcher;
+ }
+
+ protected void handleInteractionFailed(Job job, String message) {
+ jobEventService.createEvent(job, message, JobStatus.FAILED);
+ jobService.updateStatus(job, JobStatus.FAILED);
+ }
+
+ protected void checkAccess() {
+ try {
+ accessControlService.checkAccess();
+ }
+ catch (Exception exception) {
+ throw new RuntimeException(format("Access Control: Access denied (%s)",
+ exception.getMessage()));
+ }
+ }
+
+ protected void sendInfo(Job job, String message) {
+ jobEventService.createEvent(job, message);
+ }
+
+ protected void sendArtifact(Job job, InteractionArtifact result) {
+ jobArtifactService.createArtifact(
+ job,
+ result.getName(),
+ result.getFilename(),
+ result.getContentType(),
+ result.getData()
+ );
+ }
+
+ protected Model getPayloadMetadata(Job job, Resource payloadResource) {
+ return trainFetcher.fetchPayloadMetadata(payloadResource.stringValue());
+ }
+
+ protected Resource getPayloadMetadataUrl(Model model, Resource trainResource) {
+ final Value value = getObjectBy(model, trainResource, FDT.HASPAYLOAD);
+ if (value != null && value.isResource()) {
+ return (Resource) value;
+ }
+ return null;
+ }
+
+ protected String getPayloadUrl(Model model, Resource payloadResource) {
+ return getStringObjectBy(model, payloadResource, FDT.PAYLOADDOWNLOADURL);
+ }
+
+ protected void validatePayloadResource(Resource payloadResource) {
+ if (payloadResource == null) {
+ throw new RuntimeException("Validation: No payload resource found");
+ }
+ }
+
+ protected String fetchPayload(String payloadDownloadUrl) {
+ try {
+ return trainFetcher.fetchSimplePayload(payloadDownloadUrl);
+ }
+ catch (Exception exception) {
+ throw new RuntimeException(
+ format("Fetch: Failed to fetch train payload (%s)", payloadDownloadUrl));
+ }
+ }
+}
diff --git a/src/main/java/org/fairdatatrain/fairdatastation/service/interaction/train/ITrainInteraction.java b/src/main/java/org/fairdatatrain/fairdatastation/service/interaction/train/ITrainInteraction.java
new file mode 100644
index 0000000..3969b30
--- /dev/null
+++ b/src/main/java/org/fairdatatrain/fairdatastation/service/interaction/train/ITrainInteraction.java
@@ -0,0 +1,32 @@
+/**
+ * The MIT License
+ * Copyright © 2022 FAIR Data Team
+ *
+ * Permission is hereby granted, free of charge, to any person obtaining a copy
+ * of this software and associated documentation files (the "Software"), to deal
+ * in the Software without restriction, including without limitation the rights
+ * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
+ * copies of the Software, and to permit persons to whom the Software is
+ * furnished to do so, subject to the following conditions:
+ *
+ * The above copyright notice and this permission notice shall be included in
+ * all copies or substantial portions of the Software.
+ *
+ * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
+ * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+ * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
+ * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
+ * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
+ * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
+ * THE SOFTWARE.
+ */
+package org.fairdatatrain.fairdatastation.service.interaction.train;
+
+import org.eclipse.rdf4j.model.Model;
+import org.eclipse.rdf4j.model.Resource;
+import org.fairdatatrain.fairdatastation.data.model.event.Job;
+
+public interface ITrainInteraction {
+
+ void interact(Job job, Model model, Resource train);
+}
diff --git a/src/main/java/org/fairdatatrain/fairdatastation/service/interaction/train/TrainInteractionFactory.java b/src/main/java/org/fairdatatrain/fairdatastation/service/interaction/train/TrainInteractionFactory.java
new file mode 100644
index 0000000..e9271d6
--- /dev/null
+++ b/src/main/java/org/fairdatatrain/fairdatastation/service/interaction/train/TrainInteractionFactory.java
@@ -0,0 +1,52 @@
+/**
+ * The MIT License
+ * Copyright © 2022 FAIR Data Team
+ *
+ * Permission is hereby granted, free of charge, to any person obtaining a copy
+ * of this software and associated documentation files (the "Software"), to deal
+ * in the Software without restriction, including without limitation the rights
+ * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
+ * copies of the Software, and to permit persons to whom the Software is
+ * furnished to do so, subject to the following conditions:
+ *
+ * The above copyright notice and this permission notice shall be included in
+ * all copies or substantial portions of the Software.
+ *
+ * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
+ * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+ * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
+ * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
+ * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
+ * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
+ * THE SOFTWARE.
+ */
+package org.fairdatatrain.fairdatastation.service.interaction.train;
+
+import lombok.RequiredArgsConstructor;
+import lombok.extern.slf4j.Slf4j;
+import org.fairdatatrain.fairdatastation.service.interaction.entity.TrainType;
+import org.fairdatatrain.fairdatastation.service.interaction.train.fhir.FHIRTrainInteraction;
+import org.fairdatatrain.fairdatastation.service.interaction.train.sparql.SPARQLTrainInteraction;
+import org.springframework.stereotype.Component;
+
+@Slf4j
+@Component
+@RequiredArgsConstructor
+public class TrainInteractionFactory {
+
+ private final FHIRTrainInteraction fhirTrainInteraction;
+
+ private final SPARQLTrainInteraction sparqlTrainInteraction;
+
+ public ITrainInteraction getTrainInteractionService(TrainType trainType) {
+ switch (trainType) {
+ case SPARQL_TRAIN -> {
+ return sparqlTrainInteraction;
+ }
+ case FHIR_TRAIN -> {
+ return fhirTrainInteraction;
+ }
+ default -> throw new RuntimeException("Unknown train type requested.");
+ }
+ }
+}
diff --git a/src/main/java/org/fairdatatrain/fairdatastation/service/interaction/train/fhir/FHIRClient.java b/src/main/java/org/fairdatatrain/fairdatastation/service/interaction/train/fhir/FHIRClient.java
new file mode 100644
index 0000000..ba8e781
--- /dev/null
+++ b/src/main/java/org/fairdatatrain/fairdatastation/service/interaction/train/fhir/FHIRClient.java
@@ -0,0 +1,146 @@
+/**
+ * The MIT License
+ * Copyright © 2022 FAIR Data Team
+ *
+ * Permission is hereby granted, free of charge, to any person obtaining a copy
+ * of this software and associated documentation files (the "Software"), to deal
+ * in the Software without restriction, including without limitation the rights
+ * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
+ * copies of the Software, and to permit persons to whom the Software is
+ * furnished to do so, subject to the following conditions:
+ *
+ * The above copyright notice and this permission notice shall be included in
+ * all copies or substantial portions of the Software.
+ *
+ * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
+ * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+ * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
+ * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
+ * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
+ * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
+ * THE SOFTWARE.
+ */
+package org.fairdatatrain.fairdatastation.service.interaction.train.fhir;
+
+import com.fasterxml.jackson.core.JsonProcessingException;
+import com.fasterxml.jackson.databind.ObjectMapper;
+import lombok.RequiredArgsConstructor;
+import lombok.extern.slf4j.Slf4j;
+import org.fairdatatrain.fairdatastation.config.properties.FHIRProperties;
+import org.fairdatatrain.fairdatastation.service.interaction.train.fhir.request.FHIRPreparedRequest;
+import org.fairdatatrain.fairdatastation.service.interaction.train.fhir.request.FHIRRequest;
+import org.fairdatatrain.fairdatastation.service.interaction.train.fhir.request.FHIRRequestWrapper;
+import org.fairdatatrain.fairdatastation.service.interaction.train.fhir.response.FHIRResponse;
+import org.springframework.http.HttpMethod;
+import org.springframework.http.HttpStatus;
+import org.springframework.http.MediaType;
+import org.springframework.http.ResponseEntity;
+import org.springframework.stereotype.Component;
+import org.springframework.web.client.HttpClientErrorException;
+import org.springframework.web.reactive.function.client.WebClient;
+import org.springframework.web.reactive.function.client.WebClientException;
+import org.springframework.web.util.UriComponentsBuilder;
+
+import java.net.URI;
+import java.nio.charset.StandardCharsets;
+
+import static java.lang.String.format;
+import static java.util.Optional.ofNullable;
+
+@Slf4j
+@Component
+@RequiredArgsConstructor
+public class FHIRClient {
+
+ private static final MediaType ACCEPT_TYPE = MediaType.valueOf("application/fhir+json");
+
+ private final FHIRProperties fhirProperties;
+
+ private final WebClient webClient;
+
+ private final ObjectMapper objectMapper;
+
+ public boolean isReady() {
+ return fhirProperties.getBase() != null;
+ }
+
+ public FHIRResponse send(FHIRPreparedRequest request) {
+ log.debug("Sending FHIR {} request: {}", request.getMethod().name(), request.getUri());
+ try {
+ final ResponseEntity response = prepareRequestSpec(request, ACCEPT_TYPE)
+ .retrieve()
+ .toEntity(String.class)
+ .block();
+ if (response == null) {
+ log.warn("No response from FHIR API for {}", request.getUri());
+ throw new HttpClientErrorException(
+ HttpStatus.INTERNAL_SERVER_ERROR,
+ "Failed to get response from FHIR API"
+ );
+ }
+ return FHIRResponse
+ .builder()
+ .statusCode(response.getStatusCode())
+ .headers(response.getHeaders())
+ .body(response.getBody())
+ .build();
+ }
+ catch (WebClientException exception) {
+ log.warn("Request failed to FHIR API for {}: {}", request.getUri(), exception);
+ throw new RuntimeException(
+ format("Execution: Failed to communicate with FHIR API (%s)",
+ ofNullable(exception.getMessage()).orElse("Uknown error")));
+ }
+ }
+
+ public FHIRPreparedRequest parseRequest(String payload) {
+ try {
+ final FHIRRequestWrapper fhirRequestWrapper =
+ objectMapper.readValue(payload, FHIRRequestWrapper.class);
+ return toPreparedRequest(fhirRequestWrapper.getApiRequest());
+ }
+ catch (JsonProcessingException exception) {
+ throw new RuntimeException(exception);
+ }
+ }
+
+ private WebClient.RequestBodySpec prepareRequestSpec(
+ FHIRPreparedRequest request, MediaType accept) {
+ final WebClient.RequestBodySpec spec = webClient
+ .method(request.getMethod())
+ .uri(URI.create(request.getUri()))
+ .headers(headers -> request.getHeaders().forEach(headers::set))
+ .accept(accept)
+ .acceptCharset(StandardCharsets.UTF_8);
+ if (request.getMethod().equals(HttpMethod.POST)
+ || request.getMethod().equals(HttpMethod.PUT)) {
+ spec.bodyValue(request.getBody());
+ }
+ return spec;
+ }
+
+ private FHIRPreparedRequest toPreparedRequest(FHIRRequest request) {
+ final String uri = composeUri(request);
+ return FHIRPreparedRequest
+ .builder()
+ .method(HttpMethod.valueOf(request.getMethod().toUpperCase()))
+ .uri(uri)
+ .body(request.getBody())
+ .headers(request.getHeaders())
+ .build();
+ }
+
+ private String composeUri(FHIRRequest request) {
+ final UriComponentsBuilder builder =
+ UriComponentsBuilder.fromHttpUrl(fhirProperties.getBase());
+ builder.path(request.getResource());
+ request.getParameters().forEach(param -> {
+ if (param.getIsMandatory() || param.getValue() != null) {
+ builder.queryParam(param.getName(), param.getValue());
+ }
+ });
+ builder.queryParam("_pretty", true);
+ builder.queryParam("_format", "json");
+ return builder.build().toUriString();
+ }
+}
diff --git a/src/main/java/org/fairdatatrain/fairdatastation/service/interaction/train/fhir/FHIRTrainInteraction.java b/src/main/java/org/fairdatatrain/fairdatastation/service/interaction/train/fhir/FHIRTrainInteraction.java
new file mode 100644
index 0000000..ddbeab1
--- /dev/null
+++ b/src/main/java/org/fairdatatrain/fairdatastation/service/interaction/train/fhir/FHIRTrainInteraction.java
@@ -0,0 +1,173 @@
+/**
+ * The MIT License
+ * Copyright © 2022 FAIR Data Team
+ *
+ * Permission is hereby granted, free of charge, to any person obtaining a copy
+ * of this software and associated documentation files (the "Software"), to deal
+ * in the Software without restriction, including without limitation the rights
+ * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
+ * copies of the Software, and to permit persons to whom the Software is
+ * furnished to do so, subject to the following conditions:
+ *
+ * The above copyright notice and this permission notice shall be included in
+ * all copies or substantial portions of the Software.
+ *
+ * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
+ * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+ * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
+ * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
+ * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
+ * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
+ * THE SOFTWARE.
+ */
+package org.fairdatatrain.fairdatastation.service.interaction.train.fhir;
+
+import lombok.extern.slf4j.Slf4j;
+import org.eclipse.rdf4j.model.Model;
+import org.eclipse.rdf4j.model.Resource;
+import org.fairdatatrain.fairdatastation.data.model.enums.JobStatus;
+import org.fairdatatrain.fairdatastation.data.model.event.Job;
+import org.fairdatatrain.fairdatastation.service.accesscontrol.BasicAccessControlService;
+import org.fairdatatrain.fairdatastation.service.event.job.JobService;
+import org.fairdatatrain.fairdatastation.service.event.job.artifact.JobArtifactService;
+import org.fairdatatrain.fairdatastation.service.event.job.event.JobEventService;
+import org.fairdatatrain.fairdatastation.service.interaction.entity.InteractionArtifact;
+import org.fairdatatrain.fairdatastation.service.interaction.fetch.TrainFetcher;
+import org.fairdatatrain.fairdatastation.service.interaction.train.AbstractTrainInteraction;
+import org.fairdatatrain.fairdatastation.service.interaction.train.ITrainInteraction;
+import org.fairdatatrain.fairdatastation.service.interaction.train.fhir.request.FHIRPreparedRequest;
+import org.fairdatatrain.fairdatastation.service.interaction.train.fhir.response.FHIRResponse;
+import org.springframework.http.MediaType;
+import org.springframework.stereotype.Service;
+
+import java.nio.charset.StandardCharsets;
+import java.util.List;
+import java.util.Optional;
+
+@Slf4j
+@Service
+public class FHIRTrainInteraction extends AbstractTrainInteraction implements ITrainInteraction {
+
+ private final FHIRClient fhirClient;
+
+ public FHIRTrainInteraction(
+ BasicAccessControlService accessControlService,
+ JobEventService jobEventService,
+ JobArtifactService jobArtifactService,
+ JobService jobService,
+ TrainFetcher trainFetcher,
+ FHIRClient fhirClient
+ ) {
+ super(accessControlService, jobEventService, jobArtifactService, jobService, trainFetcher);
+ this.fhirClient = fhirClient;
+ }
+
+ @Override
+ public void interact(Job job, Model model, Resource train) {
+ sendInfo(job, "Processing further as FHIR train");
+ try {
+ final FHIRPreparedRequest fhirRequest = interactPrepare(job, model, train);
+
+ sendInfo(job, "Validation: Validating FHIR request");
+ validateRequest(fhirRequest);
+ sendInfo(job, "Validation: FHIR request validated");
+
+ sendInfo(job, "Access Control: Requesting access to Triple Store");
+ checkAccess();
+ sendInfo(job, "Access Control: Access to Triple Store granted");
+
+ final List results = interactCommunicate(job, fhirRequest);
+ results.forEach(result -> sendArtifact(job, result));
+
+ getJobEventService().createEvent(job, "Finished!", JobStatus.FINISHED);
+ getJobService().updateStatus(job, JobStatus.FINISHED);
+ }
+ catch (Exception exception) {
+ handleInteractionFailed(job, exception.getMessage());
+ }
+ }
+
+ public FHIRPreparedRequest interactPrepare(Job job, Model model, Resource train) {
+ final Resource payloadResource = getPayloadMetadataUrl(model, train);
+ sendInfo(job, "Validation: Validating payload resource");
+ validatePayloadResource(payloadResource);
+ sendInfo(job, "Validation: Payload resource validated");
+
+ sendInfo(job, "Fetch: Fetching payload metadata");
+ final Model payloadMetadata = getPayloadMetadata(job, payloadResource);
+ final String payloadDownloadUrl = getPayloadUrl(payloadMetadata, payloadResource);
+ sendInfo(job, "Fetch: Payload metadata fetched");
+
+ sendInfo(job, "Fetch: Fetching train payload (FHIR request)");
+ final String payload = fetchPayload(payloadDownloadUrl);
+ sendInfo(job, "Fetch: Train payload (FHIR request) fetched");
+
+ sendInfo(job, "Validation: Parsing train payload");
+ final FHIRPreparedRequest fhirRequest = prepareRequest(payload);
+ sendInfo(job, "Validation: Train payload parsed");
+
+ return fhirRequest;
+ }
+
+ public List interactCommunicate(Job job, FHIRPreparedRequest fhirRequest) {
+ sendInfo(job, "Execution: Sending FHIR request to API");
+ final FHIRResponse response = fhirClient.send(fhirRequest);
+ sendInfo(job, "Execution: FHIR response received from API");
+
+ sendInfo(job, "Validation: Validating FHIR response");
+ validateResponse(response);
+ sendInfo(job, "Validation: FHIR response validated");
+
+ sendInfo(job, "Execution: Preparing and sending artifact(s)");
+ return responseToArtifacts(response);
+ }
+
+ private List responseToArtifacts(FHIRResponse response) {
+ final MediaType contentType = Optional
+ .ofNullable(response.getHeaders().getContentType())
+ .orElse(MediaType.TEXT_PLAIN);
+ if (response.getStatusCode().is2xxSuccessful()) {
+ return List.of(
+ InteractionArtifact
+ .builder()
+ .name("FHIR Response")
+ .filename("fhir-response.json")
+ .contentType(contentType.toString())
+ .data(response.getBody().getBytes(StandardCharsets.UTF_8))
+ .build()
+ );
+ }
+ else if (response.getBody() != null && !response.getBody().isBlank()) {
+ return List.of(
+ InteractionArtifact
+ .builder()
+ .name("FHIR Error")
+ .filename("fhir-error.json")
+ .contentType(contentType.toString())
+ .data(response.getBody().getBytes(StandardCharsets.UTF_8))
+ .build()
+ );
+ }
+ return List.of();
+ }
+
+ private void validateResponse(FHIRResponse response) {
+ // TODO: validate (?)
+ }
+
+ private void validateRequest(FHIRPreparedRequest fhirRequest) {
+ if (fhirRequest == null) {
+ throw new RuntimeException("Validation: Invalid FHIR request");
+ }
+ }
+
+ private FHIRPreparedRequest prepareRequest(String payload) {
+ try {
+ return fhirClient.parseRequest(payload);
+ }
+ catch (Exception exception) {
+ exception.printStackTrace();
+ throw new RuntimeException("Validation: Failed to parse FHIR request");
+ }
+ }
+}
diff --git a/src/main/java/org/fairdatatrain/fairdatastation/service/interaction/train/fhir/request/FHIRPreparedRequest.java b/src/main/java/org/fairdatatrain/fairdatastation/service/interaction/train/fhir/request/FHIRPreparedRequest.java
new file mode 100644
index 0000000..971a41a
--- /dev/null
+++ b/src/main/java/org/fairdatatrain/fairdatastation/service/interaction/train/fhir/request/FHIRPreparedRequest.java
@@ -0,0 +1,44 @@
+/**
+ * The MIT License
+ * Copyright © 2022 FAIR Data Team
+ *
+ * Permission is hereby granted, free of charge, to any person obtaining a copy
+ * of this software and associated documentation files (the "Software"), to deal
+ * in the Software without restriction, including without limitation the rights
+ * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
+ * copies of the Software, and to permit persons to whom the Software is
+ * furnished to do so, subject to the following conditions:
+ *
+ * The above copyright notice and this permission notice shall be included in
+ * all copies or substantial portions of the Software.
+ *
+ * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
+ * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+ * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
+ * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
+ * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
+ * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
+ * THE SOFTWARE.
+ */
+package org.fairdatatrain.fairdatastation.service.interaction.train.fhir.request;
+
+import lombok.*;
+import org.springframework.http.HttpMethod;
+
+import java.util.Map;
+
+@NoArgsConstructor
+@AllArgsConstructor
+@Getter
+@Setter
+@Builder
+public class FHIRPreparedRequest {
+
+ private HttpMethod method;
+
+ private String uri;
+
+ private Map headers;
+
+ private String body;
+}
diff --git a/src/main/java/org/fairdatatrain/fairdatastation/service/interaction/train/fhir/request/FHIRRequest.java b/src/main/java/org/fairdatatrain/fairdatastation/service/interaction/train/fhir/request/FHIRRequest.java
new file mode 100644
index 0000000..f2fc97b
--- /dev/null
+++ b/src/main/java/org/fairdatatrain/fairdatastation/service/interaction/train/fhir/request/FHIRRequest.java
@@ -0,0 +1,58 @@
+/**
+ * The MIT License
+ * Copyright © 2022 FAIR Data Team
+ *
+ * Permission is hereby granted, free of charge, to any person obtaining a copy
+ * of this software and associated documentation files (the "Software"), to deal
+ * in the Software without restriction, including without limitation the rights
+ * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
+ * copies of the Software, and to permit persons to whom the Software is
+ * furnished to do so, subject to the following conditions:
+ *
+ * The above copyright notice and this permission notice shall be included in
+ * all copies or substantial portions of the Software.
+ *
+ * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
+ * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+ * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
+ * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
+ * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
+ * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
+ * THE SOFTWARE.
+ */
+package org.fairdatatrain.fairdatastation.service.interaction.train.fhir.request;
+
+import jakarta.validation.constraints.NotBlank;
+import jakarta.validation.constraints.NotNull;
+import lombok.AllArgsConstructor;
+import lombok.Getter;
+import lombok.NoArgsConstructor;
+import lombok.Setter;
+
+import java.util.List;
+import java.util.Map;
+
+@NoArgsConstructor
+@AllArgsConstructor
+@Getter
+@Setter
+public class FHIRRequest {
+
+ @NotNull
+ @NotBlank
+ private String protocol;
+
+ @NotNull
+ @NotBlank
+ private String method;
+
+ @NotNull
+ @NotBlank
+ private String resource;
+
+ private List parameters = List.of();
+
+ private String body;
+
+ private Map headers = Map.of();
+}
diff --git a/src/main/java/org/fairdatatrain/fairdatastation/service/interaction/train/fhir/request/FHIRRequestParameter.java b/src/main/java/org/fairdatatrain/fairdatastation/service/interaction/train/fhir/request/FHIRRequestParameter.java
new file mode 100644
index 0000000..e6b7599
--- /dev/null
+++ b/src/main/java/org/fairdatatrain/fairdatastation/service/interaction/train/fhir/request/FHIRRequestParameter.java
@@ -0,0 +1,46 @@
+/**
+ * The MIT License
+ * Copyright © 2022 FAIR Data Team
+ *
+ * Permission is hereby granted, free of charge, to any person obtaining a copy
+ * of this software and associated documentation files (the "Software"), to deal
+ * in the Software without restriction, including without limitation the rights
+ * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
+ * copies of the Software, and to permit persons to whom the Software is
+ * furnished to do so, subject to the following conditions:
+ *
+ * The above copyright notice and this permission notice shall be included in
+ * all copies or substantial portions of the Software.
+ *
+ * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
+ * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+ * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
+ * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
+ * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
+ * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
+ * THE SOFTWARE.
+ */
+package org.fairdatatrain.fairdatastation.service.interaction.train.fhir.request;
+
+import jakarta.validation.constraints.NotBlank;
+import jakarta.validation.constraints.NotNull;
+import lombok.AllArgsConstructor;
+import lombok.Getter;
+import lombok.NoArgsConstructor;
+import lombok.Setter;
+
+@NoArgsConstructor
+@AllArgsConstructor
+@Getter
+@Setter
+public class FHIRRequestParameter {
+
+ @NotNull
+ @NotBlank
+ private String name;
+
+ private String value;
+
+ @NotNull
+ private Boolean isMandatory;
+}
diff --git a/src/main/java/org/fairdatatrain/fairdatastation/service/interaction/train/fhir/request/FHIRRequestWrapper.java b/src/main/java/org/fairdatatrain/fairdatastation/service/interaction/train/fhir/request/FHIRRequestWrapper.java
new file mode 100644
index 0000000..8fabe21
--- /dev/null
+++ b/src/main/java/org/fairdatatrain/fairdatastation/service/interaction/train/fhir/request/FHIRRequestWrapper.java
@@ -0,0 +1,39 @@
+/**
+ * The MIT License
+ * Copyright © 2022 FAIR Data Team
+ *
+ * Permission is hereby granted, free of charge, to any person obtaining a copy
+ * of this software and associated documentation files (the "Software"), to deal
+ * in the Software without restriction, including without limitation the rights
+ * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
+ * copies of the Software, and to permit persons to whom the Software is
+ * furnished to do so, subject to the following conditions:
+ *
+ * The above copyright notice and this permission notice shall be included in
+ * all copies or substantial portions of the Software.
+ *
+ * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
+ * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+ * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
+ * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
+ * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
+ * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
+ * THE SOFTWARE.
+ */
+package org.fairdatatrain.fairdatastation.service.interaction.train.fhir.request;
+
+import com.fasterxml.jackson.annotation.JsonProperty;
+import lombok.AllArgsConstructor;
+import lombok.Getter;
+import lombok.NoArgsConstructor;
+import lombok.Setter;
+
+@NoArgsConstructor
+@AllArgsConstructor
+@Getter
+@Setter
+public class FHIRRequestWrapper {
+
+ @JsonProperty("APIRequest")
+ private FHIRRequest apiRequest;
+}
diff --git a/src/main/java/org/fairdatatrain/fairdatastation/service/interaction/train/fhir/response/FHIRResponse.java b/src/main/java/org/fairdatatrain/fairdatastation/service/interaction/train/fhir/response/FHIRResponse.java
new file mode 100644
index 0000000..872dcd8
--- /dev/null
+++ b/src/main/java/org/fairdatatrain/fairdatastation/service/interaction/train/fhir/response/FHIRResponse.java
@@ -0,0 +1,41 @@
+/**
+ * The MIT License
+ * Copyright © 2022 FAIR Data Team
+ *
+ * Permission is hereby granted, free of charge, to any person obtaining a copy
+ * of this software and associated documentation files (the "Software"), to deal
+ * in the Software without restriction, including without limitation the rights
+ * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
+ * copies of the Software, and to permit persons to whom the Software is
+ * furnished to do so, subject to the following conditions:
+ *
+ * The above copyright notice and this permission notice shall be included in
+ * all copies or substantial portions of the Software.
+ *
+ * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
+ * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+ * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
+ * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
+ * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
+ * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
+ * THE SOFTWARE.
+ */
+package org.fairdatatrain.fairdatastation.service.interaction.train.fhir.response;
+
+import lombok.*;
+import org.springframework.http.HttpHeaders;
+import org.springframework.http.HttpStatusCode;
+
+@NoArgsConstructor
+@AllArgsConstructor
+@Getter
+@Setter
+@Builder
+public class FHIRResponse {
+
+ private HttpStatusCode statusCode;
+
+ private HttpHeaders headers;
+
+ private String body;
+}
diff --git a/src/main/java/org/fairdatatrain/fairdatastation/service/interaction/train/sparql/SPARQLTrainInteraction.java b/src/main/java/org/fairdatatrain/fairdatastation/service/interaction/train/sparql/SPARQLTrainInteraction.java
new file mode 100644
index 0000000..fbd955a
--- /dev/null
+++ b/src/main/java/org/fairdatatrain/fairdatastation/service/interaction/train/sparql/SPARQLTrainInteraction.java
@@ -0,0 +1,145 @@
+/**
+ * The MIT License
+ * Copyright © 2022 FAIR Data Team
+ *
+ * Permission is hereby granted, free of charge, to any person obtaining a copy
+ * of this software and associated documentation files (the "Software"), to deal
+ * in the Software without restriction, including without limitation the rights
+ * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
+ * copies of the Software, and to permit persons to whom the Software is
+ * furnished to do so, subject to the following conditions:
+ *
+ * The above copyright notice and this permission notice shall be included in
+ * all copies or substantial portions of the Software.
+ *
+ * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
+ * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+ * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
+ * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
+ * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
+ * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
+ * THE SOFTWARE.
+ */
+package org.fairdatatrain.fairdatastation.service.interaction.train.sparql;
+
+import lombok.extern.slf4j.Slf4j;
+import nl.dtls.fairdatapoint.vocabulary.FDT;
+import org.eclipse.rdf4j.model.Model;
+import org.eclipse.rdf4j.model.Resource;
+import org.eclipse.rdf4j.query.QueryLanguage;
+import org.eclipse.rdf4j.query.parser.ParsedOperation;
+import org.eclipse.rdf4j.query.parser.ParsedUpdate;
+import org.eclipse.rdf4j.query.parser.QueryParserUtil;
+import org.fairdatatrain.fairdatastation.data.model.enums.JobStatus;
+import org.fairdatatrain.fairdatastation.data.model.event.Job;
+import org.fairdatatrain.fairdatastation.service.accesscontrol.BasicAccessControlService;
+import org.fairdatatrain.fairdatastation.service.event.job.JobService;
+import org.fairdatatrain.fairdatastation.service.event.job.artifact.JobArtifactService;
+import org.fairdatatrain.fairdatastation.service.event.job.event.JobEventService;
+import org.fairdatatrain.fairdatastation.service.interaction.entity.InteractionArtifact;
+import org.fairdatatrain.fairdatastation.service.interaction.fetch.TrainFetcher;
+import org.fairdatatrain.fairdatastation.service.interaction.train.AbstractTrainInteraction;
+import org.fairdatatrain.fairdatastation.service.interaction.train.ITrainInteraction;
+import org.fairdatatrain.fairdatastation.service.storage.TripleStoreStorage;
+import org.springframework.stereotype.Service;
+
+import java.util.List;
+
+import static java.lang.String.format;
+import static org.fairdatatrain.fairdatastation.utils.RdfUtils.getStringObjectBy;
+
+@Slf4j
+@Service
+public class SPARQLTrainInteraction extends AbstractTrainInteraction implements ITrainInteraction {
+
+ private final TripleStoreStorage tripleStoreStorage;
+
+ public SPARQLTrainInteraction(
+ BasicAccessControlService accessControlService,
+ JobEventService jobEventService,
+ JobArtifactService jobArtifactService,
+ JobService jobService,
+ TrainFetcher trainFetcher,
+ TripleStoreStorage tripleStoreStorage
+ ) {
+ super(accessControlService, jobEventService, jobArtifactService, jobService, trainFetcher);
+ this.tripleStoreStorage = tripleStoreStorage;
+ }
+
+ @Override
+ public void interact(Job job, Model model, Resource train) {
+ sendInfo(job, "Processing further as SPARQL train");
+ try {
+ final Resource payloadResource = getPayloadMetadataUrl(model, train);
+ sendInfo(job, "Validation: Validating payload resource");
+ validatePayloadResource(payloadResource);
+ sendInfo(job, "Validation: Payload resource validated");
+
+ sendInfo(job, "Fetch: Fetching payload metadata");
+ final Model payloadMetadata = getPayloadMetadata(job, payloadResource);
+ sendInfo(job, "Fetch: Payload metadata fetched");
+
+ sendInfo(job, "Validation: Validating payload metadata");
+ validatePayloadMetadata(job, payloadMetadata, payloadResource);
+ final String payloadDownloadUrl = getPayloadUrl(payloadMetadata, payloadResource);
+ sendInfo(job, "Validation: Payload metadata validated");
+
+ sendInfo(job, "Fetch: Fetching train payload (SPARQL query)");
+ final String sparqlQuery = fetchPayload(payloadDownloadUrl);
+ sendInfo(job, "Fetch: Train payload (SPARQL query) fetched");
+
+ sendInfo(job, "Validation: Validating train payload");
+ validateSparqlQuery(sparqlQuery);
+ sendInfo(job, "Validation: Train payload validated");
+
+ sendInfo(job, "Access Control: Requesting access to Triple Store");
+ checkAccess();
+ sendInfo(job, "Access Control: Access to Triple Store granted");
+
+ sendInfo(job, "Execution: Executing query from SPARQL train");
+ final List results = executeQuery(sparqlQuery);
+ sendInfo(job, "Execution: Processing query result");
+
+ sendInfo(job, "Execution: Preparing and sending artifact(s)");
+ results.forEach(result -> sendArtifact(job, result));
+
+ getJobEventService().createEvent(job, "Finished!", JobStatus.FINISHED);
+ getJobService().updateStatus(job, JobStatus.FINISHED);
+ }
+ catch (Exception exception) {
+ handleInteractionFailed(job, exception.getMessage());
+ }
+ }
+
+ private List executeQuery(String sparqlQuery) {
+ try {
+ // TODO: set accept + name based on possibilities/train metadata?
+ return tripleStoreStorage.executeQuery(sparqlQuery, "Result", "*/*");
+ }
+ catch (Exception exception) {
+ throw new RuntimeException(format("Execution: Failed to execute SPARQL query (%s)",
+ exception.getMessage()));
+ }
+ }
+
+ private void validatePayloadMetadata(Job job, Model model, Resource payloadResource) {
+ if (getStringObjectBy(model, payloadResource, FDT.PAYLOADDOWNLOADURL) == null) {
+ throw new RuntimeException("Validation: Missing payload download URL");
+ }
+ }
+
+ private void validateSparqlQuery(String sparqlQuery) {
+ // parse and check non-updating
+ try {
+ final ParsedOperation operation =
+ QueryParserUtil.parseOperation(QueryLanguage.SPARQL, sparqlQuery, null);
+ if (operation instanceof ParsedUpdate) {
+ throw new RuntimeException("Validation: SPARQL Query not valid (update query)");
+ }
+ }
+ catch (Exception exception) {
+ throw new RuntimeException(format("Validation: SPARQL Query not valid (%s)",
+ exception.getMessage()));
+ }
+ }
+}
diff --git a/src/main/java/org/fairdatatrain/fairdatastation/service/storage/TripleStoreStorage.java b/src/main/java/org/fairdatatrain/fairdatastation/service/storage/TripleStoreStorage.java
new file mode 100644
index 0000000..0125279
--- /dev/null
+++ b/src/main/java/org/fairdatatrain/fairdatastation/service/storage/TripleStoreStorage.java
@@ -0,0 +1,190 @@
+/**
+ * The MIT License
+ * Copyright © 2022 FAIR Data Team
+ *
+ * Permission is hereby granted, free of charge, to any person obtaining a copy
+ * of this software and associated documentation files (the "Software"), to deal
+ * in the Software without restriction, including without limitation the rights
+ * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
+ * copies of the Software, and to permit persons to whom the Software is
+ * furnished to do so, subject to the following conditions:
+ *
+ * The above copyright notice and this permission notice shall be included in
+ * all copies or substantial portions of the Software.
+ *
+ * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
+ * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+ * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
+ * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
+ * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
+ * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
+ * THE SOFTWARE.
+ */
+package org.fairdatatrain.fairdatastation.service.storage;
+
+import lombok.RequiredArgsConstructor;
+import lombok.extern.slf4j.Slf4j;
+import org.eclipse.rdf4j.common.lang.FileFormat;
+import org.eclipse.rdf4j.query.*;
+import org.eclipse.rdf4j.query.resultio.*;
+import org.eclipse.rdf4j.repository.Repository;
+import org.eclipse.rdf4j.repository.RepositoryConnection;
+import org.eclipse.rdf4j.repository.RepositoryException;
+import org.eclipse.rdf4j.rio.RDFFormat;
+import org.eclipse.rdf4j.rio.RDFHandler;
+import org.eclipse.rdf4j.rio.Rio;
+import org.fairdatatrain.fairdatastation.exception.StorageException;
+import org.fairdatatrain.fairdatastation.service.interaction.entity.InteractionArtifact;
+import org.springframework.stereotype.Service;
+import org.springframework.util.MimeTypeUtils;
+
+import java.io.ByteArrayOutputStream;
+import java.nio.charset.StandardCharsets;
+import java.util.*;
+import java.util.stream.Collectors;
+
+import static java.lang.String.format;
+import static org.fairdatatrain.fairdatastation.utils.StringUtils.sanitizeFilename;
+
+@Slf4j
+@Service
+@RequiredArgsConstructor
+public class TripleStoreStorage {
+
+ private final Repository repository;
+
+ public boolean isReady() {
+ return repository != null;
+ }
+
+ public List executeQuery(
+ String sparqlQuery, String name, String accept
+ ) throws StorageException {
+ return executeQuery(sparqlQuery, name, Set.of(accept));
+ }
+
+ public List executeQuery(
+ String sparqlQuery, String name, Set accept
+ ) throws StorageException {
+ if (accept.isEmpty()) {
+ return List.of();
+ }
+ try (RepositoryConnection connection = repository.getConnection()) {
+ final Query query = connection.prepareQuery(QueryLanguage.SPARQL, sparqlQuery);
+
+ // SELECT
+ if (query instanceof final TupleQuery selectQuery) {
+ return evaluateQuery(selectQuery, name, accept);
+ }
+ // ASK
+ else if (query instanceof final BooleanQuery askQuery) {
+ return evaluateQuery(askQuery, name, accept);
+ }
+ // DESCRIBE / CONSTRUCT
+ else if (query instanceof final GraphQuery graphQuery) {
+ return evaluateQuery(graphQuery, name, accept);
+ }
+ // Other (e.g. UPDATE)
+ else {
+ throw new StorageException(format("Unsupported query type: %s",
+ query.getClass().getName()));
+ }
+ }
+ catch (RepositoryException exception) {
+ throw new StorageException(exception.getMessage());
+ }
+ }
+
+ private List evaluateQuery(
+ TupleQuery query, String name, Set accept
+ ) {
+ final Set formats = accept
+ .parallelStream()
+ .map(QueryResultIO::getWriterFormatForMIMEType)
+ .filter(Optional::isPresent)
+ .map(Optional::get)
+ .filter(format -> format instanceof TupleQueryResultFormat)
+ .map(format -> (TupleQueryResultFormat) format)
+ .collect(Collectors.toSet());
+ if (accept.contains(MimeTypeUtils.ALL_VALUE)) {
+ formats.add(TupleQueryResultFormat.JSON);
+ }
+ return formats
+ .parallelStream()
+ .map(format -> getQueryResult(query, name, format))
+ .toList();
+ }
+
+ private List evaluateQuery(
+ BooleanQuery query, String name, Set accept
+ ) {
+ final Set formats = accept
+ .parallelStream()
+ .map(QueryResultIO::getBooleanParserFormatForMIMEType)
+ .filter(Optional::isPresent)
+ .map(Optional::get)
+ .filter(format -> format instanceof BooleanQueryResultFormat)
+ .map(format -> (BooleanQueryResultFormat) format)
+ .collect(Collectors.toSet());
+ if (accept.contains(MimeTypeUtils.ALL_VALUE)) {
+ formats.add(BooleanQueryResultFormat.TEXT);
+ }
+ return formats
+ .parallelStream()
+ .map(format -> getQueryResult(query, name, format))
+ .toList();
+ }
+
+ private List evaluateQuery(
+ GraphQuery query, String name, Set accept
+ ) {
+ final Set formats = accept
+ .parallelStream()
+ .map(Rio::getParserFormatForMIMEType)
+ .filter(Optional::isPresent)
+ .map(Optional::get)
+ .collect(Collectors.toSet());
+ if (accept.contains(MimeTypeUtils.ALL_VALUE)) {
+ formats.add(RDFFormat.TURTLE);
+ }
+ return formats
+ .parallelStream()
+ .map(format -> getQueryResult(query, name, format))
+ .toList();
+ }
+
+ private InteractionArtifact getQueryResult(
+ TupleQuery query, String name, TupleQueryResultFormat format
+ ) {
+ final ByteArrayOutputStream bao = new ByteArrayOutputStream();
+ final TupleQueryResultWriter writer = QueryResultIO.createTupleWriter(format, bao);
+ query.evaluate(writer);
+ return createArtifact(name, format, bao.toString().getBytes(StandardCharsets.UTF_8));
+ }
+
+ private InteractionArtifact getQueryResult(
+ BooleanQuery query, String name, BooleanQueryResultFormat format
+ ) {
+ final ByteArrayOutputStream bao = new ByteArrayOutputStream();
+ final BooleanQueryResultWriter writer = QueryResultIO.createBooleanWriter(format, bao);
+ writer.handleBoolean(query.evaluate());
+ return createArtifact(name, format, bao.toString().getBytes(StandardCharsets.UTF_8));
+ }
+
+ private InteractionArtifact getQueryResult(GraphQuery query, String name, RDFFormat format) {
+ final ByteArrayOutputStream bao = new ByteArrayOutputStream();
+ final RDFHandler writer = Rio.createWriter(format, bao);
+ query.evaluate(writer);
+ return createArtifact(name, format, bao.toString().getBytes(StandardCharsets.UTF_8));
+ }
+
+ private InteractionArtifact createArtifact(String name, FileFormat format, byte[] data) {
+ return InteractionArtifact.builder()
+ .name(format("%s (%s)", name, format.getName()))
+ .filename(format("%s.%s", sanitizeFilename(name), format.getDefaultFileExtension()))
+ .contentType(format.getDefaultMIMEType())
+ .data(data)
+ .build();
+ }
+
+}
diff --git a/src/main/java/org/fairdatatrain/fairdatastation/service/validation/TrainValidationService.java b/src/main/java/org/fairdatatrain/fairdatastation/service/validation/TrainValidationService.java
new file mode 100644
index 0000000..213f55d
--- /dev/null
+++ b/src/main/java/org/fairdatatrain/fairdatastation/service/validation/TrainValidationService.java
@@ -0,0 +1,107 @@
+/**
+ * The MIT License
+ * Copyright © 2022 FAIR Data Team
+ *
+ * Permission is hereby granted, free of charge, to any person obtaining a copy
+ * of this software and associated documentation files (the "Software"), to deal
+ * in the Software without restriction, including without limitation the rights
+ * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
+ * copies of the Software, and to permit persons to whom the Software is
+ * furnished to do so, subject to the following conditions:
+ *
+ * The above copyright notice and this permission notice shall be included in
+ * all copies or substantial portions of the Software.
+ *
+ * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
+ * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+ * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
+ * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
+ * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
+ * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
+ * THE SOFTWARE.
+ */
+package org.fairdatatrain.fairdatastation.service.validation;
+
+import lombok.SneakyThrows;
+import lombok.extern.slf4j.Slf4j;
+import nl.dtls.fairdatapoint.vocabulary.FDT;
+import org.eclipse.rdf4j.model.Model;
+import org.eclipse.rdf4j.model.Resource;
+import org.eclipse.rdf4j.model.Value;
+import org.eclipse.rdf4j.model.vocabulary.DCTERMS;
+import org.eclipse.rdf4j.model.vocabulary.RDF;
+import org.fairdatatrain.fairdatastation.service.interaction.entity.TrainType;
+import org.fairdatatrain.fairdatastation.service.interaction.train.fhir.FHIRClient;
+import org.fairdatatrain.fairdatastation.service.storage.TripleStoreStorage;
+import org.springframework.stereotype.Service;
+
+import java.util.*;
+
+import static java.lang.String.format;
+
+@Slf4j
+@Service
+public class TrainValidationService {
+
+ private final Map trainTypes = new HashMap<>();
+
+ public TrainValidationService(TripleStoreStorage tripleStoreStorage, FHIRClient fhirClient) {
+ // TODO: refactor check for supported trains based on configuration/properties
+ if (tripleStoreStorage.isReady()) {
+ log.info("Supported train: SPARQL Train");
+ trainTypes.put(FDT.SPARQLTRAIN, TrainType.SPARQL_TRAIN);
+ }
+ if (fhirClient.isReady()) {
+ log.info("Supported train: FHIR Train");
+ trainTypes.put(FDT.FHIRTRAIN, TrainType.FHIR_TRAIN);
+ }
+ }
+
+ @SneakyThrows
+ public Resource validate(Model trainModel) {
+ List trainsFound = new ArrayList<>();
+ for (Resource trainType : trainTypes.keySet()) {
+ trainsFound.addAll(trainModel.filter(null, RDF.TYPE, trainType).subjects());
+ }
+ for (Resource trainType : trainTypes.keySet()) {
+ // Why DCTERMS?
+ trainsFound.addAll(trainModel.filter(null, DCTERMS.TYPE, trainType).subjects());
+ }
+ trainsFound.addAll(trainModel.filter(null, RDF.TYPE, FDT.TRAIN).subjects());
+ trainsFound = trainsFound.stream().distinct().toList();
+ if (trainsFound.isEmpty()) {
+ throw new RuntimeException("Validation: No train specification found in metadata");
+ }
+ if (trainsFound.size() > 1) {
+ throw new RuntimeException(
+ format("Validation: More than one train found in metadata: %s", trainsFound));
+ }
+ final Resource train = trainsFound.get(0);
+ // TODO: more validations (train metadata)
+ return train;
+ }
+
+ public TrainType determineTrainType(Model trainModel, Resource train) {
+ final List trainTypeResources = new ArrayList<>();
+ trainTypeResources.addAll(trainModel.filter(train, RDF.TYPE, null).objects());
+ trainTypeResources.addAll(trainModel.filter(train, DCTERMS.TYPE, null).objects());
+ final List presentTrainTypes = trainTypeResources
+ .stream()
+ .distinct()
+ .filter(Value::isResource)
+ .map(value -> (Resource) value)
+ .map(trainTypes::get)
+ .filter(Objects::nonNull)
+ .toList();
+ if (presentTrainTypes.isEmpty()) {
+ throw new RuntimeException("Validation: No supported train type found in metadata");
+ }
+ if (presentTrainTypes.size() > 1) {
+ throw new RuntimeException(
+ format("Validation: Multiple supported train types found in metadata: %s",
+ presentTrainTypes)
+ );
+ }
+ return presentTrainTypes.get(0);
+ }
+}
diff --git a/src/main/java/org/fairdatatrain/fairdatastation/utils/HashUtils.java b/src/main/java/org/fairdatatrain/fairdatastation/utils/HashUtils.java
new file mode 100644
index 0000000..0616d98
--- /dev/null
+++ b/src/main/java/org/fairdatatrain/fairdatastation/utils/HashUtils.java
@@ -0,0 +1,39 @@
+/**
+ * The MIT License
+ * Copyright © 2022 FAIR Data Team
+ *
+ * Permission is hereby granted, free of charge, to any person obtaining a copy
+ * of this software and associated documentation files (the "Software"), to deal
+ * in the Software without restriction, including without limitation the rights
+ * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
+ * copies of the Software, and to permit persons to whom the Software is
+ * furnished to do so, subject to the following conditions:
+ *
+ * The above copyright notice and this permission notice shall be included in
+ * all copies or substantial portions of the Software.
+ *
+ * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
+ * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+ * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
+ * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
+ * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
+ * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
+ * THE SOFTWARE.
+ */
+package org.fairdatatrain.fairdatastation.utils;
+
+public class HashUtils {
+ private static final int MASK = 0xff;
+
+ public static String bytesToHex(byte[] hash) {
+ final StringBuilder hexString = new StringBuilder(2 * hash.length);
+ for (byte b : hash) {
+ final String hex = Integer.toHexString(MASK & b);
+ if (hex.length() == 1) {
+ hexString.append('0');
+ }
+ hexString.append(hex);
+ }
+ return hexString.toString();
+ }
+}
diff --git a/src/main/java/org/fairdatatrain/fairdatastation/utils/HttpUtils.java b/src/main/java/org/fairdatatrain/fairdatastation/utils/HttpUtils.java
new file mode 100644
index 0000000..7d4b097
--- /dev/null
+++ b/src/main/java/org/fairdatatrain/fairdatastation/utils/HttpUtils.java
@@ -0,0 +1,36 @@
+/**
+ * The MIT License
+ * Copyright © 2022 FAIR Data Team
+ *
+ * Permission is hereby granted, free of charge, to any person obtaining a copy
+ * of this software and associated documentation files (the "Software"), to deal
+ * in the Software without restriction, including without limitation the rights
+ * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
+ * copies of the Software, and to permit persons to whom the Software is
+ * furnished to do so, subject to the following conditions:
+ *
+ * The above copyright notice and this permission notice shall be included in
+ * all copies or substantial portions of the Software.
+ *
+ * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
+ * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+ * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
+ * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
+ * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
+ * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
+ * THE SOFTWARE.
+ */
+package org.fairdatatrain.fairdatastation.utils;
+
+public class HttpUtils {
+
+ private static final String URL_SEP = "/";
+
+ public static String removeLastSlash(String url) {
+ String fixedUrl = url;
+ while (fixedUrl.endsWith(URL_SEP)) {
+ fixedUrl = fixedUrl.substring(0, fixedUrl.length() - 1);
+ }
+ return fixedUrl;
+ }
+}
diff --git a/src/main/java/org/fairdatatrain/fairdatastation/utils/RdfUtils.java b/src/main/java/org/fairdatatrain/fairdatastation/utils/RdfUtils.java
new file mode 100644
index 0000000..2f117b0
--- /dev/null
+++ b/src/main/java/org/fairdatatrain/fairdatastation/utils/RdfUtils.java
@@ -0,0 +1,137 @@
+/**
+ * The MIT License
+ * Copyright © 2022 FAIR Data Team
+ *
+ * Permission is hereby granted, free of charge, to any person obtaining a copy
+ * of this software and associated documentation files (the "Software"), to deal
+ * in the Software without restriction, including without limitation the rights
+ * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
+ * copies of the Software, and to permit persons to whom the Software is
+ * furnished to do so, subject to the following conditions:
+ *
+ * The above copyright notice and this permission notice shall be included in
+ * all copies or substantial portions of the Software.
+ *
+ * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
+ * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+ * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
+ * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
+ * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
+ * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
+ * THE SOFTWARE.
+ */
+package org.fairdatatrain.fairdatastation.utils;
+
+import org.eclipse.rdf4j.model.*;
+import org.eclipse.rdf4j.model.util.Models;
+import org.eclipse.rdf4j.model.util.Values;
+import org.eclipse.rdf4j.model.vocabulary.*;
+import org.eclipse.rdf4j.rio.*;
+import org.eclipse.rdf4j.rio.helpers.BasicWriterSettings;
+
+import java.io.ByteArrayInputStream;
+import java.io.IOException;
+import java.io.InputStream;
+import java.io.StringWriter;
+import java.util.List;
+import java.util.Optional;
+
+public class RdfUtils {
+
+ private static final String PREFIX_SEP = ":";
+
+ private static final WriterConfig WRITER_CONFIG = new WriterConfig();
+
+ static {
+ WRITER_CONFIG.set(BasicWriterSettings.INLINE_BLANK_NODES, true);
+ }
+
+ public static IRI i(String iri) {
+ if (iri == null) {
+ return null;
+ }
+ return Values.iri(iri);
+ }
+
+ public static IRI i(String iri, Model model) {
+ if (iri != null) {
+ // URI: ':title'
+ if (iri.startsWith(PREFIX_SEP)) {
+ final Optional optionalNamespace = model.getNamespace("");
+ final String prefix = optionalNamespace.get().getName();
+ return i(prefix + iri.substring(1));
+ }
+
+ // URI: 'rda:title'
+ final String[] splitted = iri.split(PREFIX_SEP);
+ if (splitted.length == 2 && splitted[1].charAt(0) != '/') {
+ final Optional optionalNamespace = model.getNamespace(splitted[0]);
+ final String prefix = optionalNamespace.get().getName();
+ return i(prefix + splitted[1]);
+ }
+
+ // URI: 'http://schema.org/person#title'
+ if (iri.contains("://")) {
+ return i(iri);
+ }
+
+ }
+ return null;
+ }
+
+ public static List getObjectsBy(Model model, Resource subject, IRI predicate) {
+ return Models.getProperties(model, subject, predicate).stream().toList();
+ }
+
+ public static List getObjectsBy(Model model, String subject, String predicate) {
+ return getObjectsBy(model, i(subject, model), i(predicate, model));
+ }
+
+ public static Value getObjectBy(Model model, Resource subject, IRI predicate) {
+ return Models.getProperty(model, subject, predicate).orElse(null);
+ }
+
+ public static String getStringObjectBy(Model model, Resource subject, IRI predicate) {
+ return Models.getProperty(model, subject, predicate).map(Value::stringValue).orElse(null);
+ }
+
+ public static Model read(String content, String baseUri) {
+ return read(content, baseUri, RDFFormat.TURTLE);
+ }
+
+ public static Model read(String content, String baseUri, RDFFormat format) {
+ try (InputStream inputStream = new ByteArrayInputStream(content.getBytes())) {
+ return Rio.parse(inputStream, baseUri, format);
+ }
+ catch (IOException exception) {
+ throw new RuntimeException("Unable to read RDF (IO exception)");
+ }
+ catch (RDFParseException exception) {
+ throw new RuntimeException("Unable to read RDF (parse exception)");
+ }
+ catch (RDFHandlerException exception) {
+ throw new RuntimeException("Unable to read RDF (handler exception)");
+ }
+ }
+
+ public static String write(Model model) {
+ return write(model, RDFFormat.TURTLE);
+ }
+
+ public static String write(Model model, RDFFormat format) {
+ model.setNamespace(DCTERMS.NS);
+ model.setNamespace(DCAT.NS);
+ model.setNamespace(FOAF.NS);
+ model.setNamespace(XSD.NS);
+ model.setNamespace(LDP.NS);
+
+ try (StringWriter out = new StringWriter()) {
+ Rio.write(model, out, format, WRITER_CONFIG);
+ return out.toString();
+ }
+ catch (IOException exception) {
+ throw new RuntimeException("Unable to write RDF (IO exception)");
+ }
+ }
+
+}
diff --git a/src/main/java/org/fairdatatrain/fairdatastation/utils/StringUtils.java b/src/main/java/org/fairdatatrain/fairdatastation/utils/StringUtils.java
new file mode 100644
index 0000000..a269f1e
--- /dev/null
+++ b/src/main/java/org/fairdatatrain/fairdatastation/utils/StringUtils.java
@@ -0,0 +1,33 @@
+/**
+ * The MIT License
+ * Copyright © 2022 FAIR Data Team
+ *
+ * Permission is hereby granted, free of charge, to any person obtaining a copy
+ * of this software and associated documentation files (the "Software"), to deal
+ * in the Software without restriction, including without limitation the rights
+ * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
+ * copies of the Software, and to permit persons to whom the Software is
+ * furnished to do so, subject to the following conditions:
+ *
+ * The above copyright notice and this permission notice shall be included in
+ * all copies or substantial portions of the Software.
+ *
+ * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
+ * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+ * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
+ * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
+ * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
+ * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
+ * THE SOFTWARE.
+ */
+package org.fairdatatrain.fairdatastation.utils;
+
+public class StringUtils {
+
+ public static String sanitizeFilename(String filename) {
+ return filename
+ .replaceAll("[^a-zA-Z0-9.-]", "_")
+ .toLowerCase();
+ }
+
+}
diff --git a/src/main/java/org/fairdatatrain/fairdatastation/utils/TimeUtils.java b/src/main/java/org/fairdatatrain/fairdatastation/utils/TimeUtils.java
new file mode 100644
index 0000000..9464ff4
--- /dev/null
+++ b/src/main/java/org/fairdatatrain/fairdatastation/utils/TimeUtils.java
@@ -0,0 +1,33 @@
+/**
+ * The MIT License
+ * Copyright © 2022 FAIR Data Team
+ *
+ * Permission is hereby granted, free of charge, to any person obtaining a copy
+ * of this software and associated documentation files (the "Software"), to deal
+ * in the Software without restriction, including without limitation the rights
+ * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
+ * copies of the Software, and to permit persons to whom the Software is
+ * furnished to do so, subject to the following conditions:
+ *
+ * The above copyright notice and this permission notice shall be included in
+ * all copies or substantial portions of the Software.
+ *
+ * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
+ * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+ * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
+ * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
+ * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
+ * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
+ * THE SOFTWARE.
+ */
+package org.fairdatatrain.fairdatastation.utils;
+
+import java.sql.Timestamp;
+import java.time.Instant;
+
+public class TimeUtils {
+
+ public static Timestamp now() {
+ return Timestamp.from(Instant.now());
+ }
+}
diff --git a/src/main/resources/application-dev.yml b/src/main/resources/application-dev.yml
new file mode 100644
index 0000000..c5bc0e0
--- /dev/null
+++ b/src/main/resources/application-dev.yml
@@ -0,0 +1,13 @@
+data-station:
+ fdp-url: http://localhost:8088
+
+spring:
+ datasource:
+ url: jdbc:postgresql://localhost/fds
+ username: postgres
+ password: password
+ flyway:
+ locations: classpath:db/migration,classpath:dev/db/migration
+
+server:
+ port: 8082
diff --git a/src/main/resources/application.yml b/src/main/resources/application.yml
new file mode 100644
index 0000000..ab76943
--- /dev/null
+++ b/src/main/resources/application.yml
@@ -0,0 +1,79 @@
+data-station:
+ fdp-url: ${FDS_FDP_URL:http://fdp}
+ storages:
+ triple-store:
+ # valid repository type options {1 = inMemoryStore, 2 = NativeStore, 3 = AllegroGraph, 4 = graphDB, 5 = blazegraph}
+ type: ${FDS_TRIPLE_STORE_TYPE:1}
+ native:
+ dir: ${FDS_TRIPLE_STORE_DIR:/tmp/fdp-store/}
+ agraph:
+ url: ${FDS_TRIPLE_STORE_URL:http://localhost:10035/repositories/fdp}
+ username: ${FDS_TRIPLE_STORE_USERNAME:user}
+ password: ${FDS_TRIPLE_STORE_PASSWORD:password}
+ graphDb:
+ url: ${FDS_TRIPLE_STORE_URL:http://localhost:7200}
+ repository: ${FDS_TRIPLE_STORE_REPOSITORY:test}
+ username: ${FDS_TRIPLE_STORE_USERNAME:user}
+ password: ${FDS_TRIPLE_STORE_PASSWORD:password}
+ blazegraph:
+ url: ${FDS_TRIPLE_STORE_URL:http://localhost:8888/blazegraph}
+ repository: ${FDS_TRIPLE_STORE_REPOSITORY:test}
+ apis:
+ fhir:
+ base: ${FDS_FHIR_BASE_URL}
+
+
+spring:
+ application:
+ name: fair-data-station
+ task:
+ scheduling:
+ pool:
+ size: 2
+ main:
+ banner-mode: off
+ web-application-type: reactive
+ datasource:
+ url: jdbc:postgresql://${FDS_POSTGRES_HOST:postgres}:${FDS_POSTGRES_PORT:5432}/${FDS_POSTGRES_DB:fds}
+ username: ${FDS_POSTGRES_USERNAME:postgres}
+ password: ${FDS_POSTGRES_PASSWORD:password}
+ flyway:
+ locations: classpath:db/migration
+ jpa:
+ properties:
+ hibernate:
+ ddl-auto: validate
+ dialect: org.hibernate.dialect.PostgreSQLDialect
+ jdbc:
+ time_zone: UTC
+ cloud:
+ gateway:
+ globalcors:
+ corsConfigurations:
+ '[/**]':
+ allowedOrigins: "*"
+ allowedMethods: "*"
+ enabled: true
+
+springdoc:
+ swagger-ui:
+ disable-swagger-default-url: true
+ tags-sorter: alpha
+ operations-sorter: alpha
+ webjars:
+ prefix: ''
+
+management:
+ health:
+ solr:
+ enabled: false
+ info:
+ defaults:
+ enabled: false
+ endpoints:
+ web:
+ exposure:
+ include: health, info, gateway
+ cors:
+ allowed-origins: "*"
+ allowed-methods: "GET"
diff --git a/src/main/resources/db/migration/V0001.0__init-db.sql b/src/main/resources/db/migration/V0001.0__init-db.sql
new file mode 100644
index 0000000..78be0a1
--- /dev/null
+++ b/src/main/resources/db/migration/V0001.0__init-db.sql
@@ -0,0 +1,80 @@
+CREATE TYPE job_status AS ENUM (
+ 'PREPARED',
+ 'QUEUED',
+ 'RUNNING',
+ 'FINISHED',
+ 'ABORTING',
+ 'ABORTED',
+ 'ERRORED',
+ 'FAILED'
+ );
+
+CREATE CAST (character varying AS job_status) WITH INOUT AS ASSIGNMENT;
+
+CREATE TYPE artifact_storage AS ENUM (
+ 'POSTGRES',
+ 'S3',
+ 'LOCALFS'
+ );
+
+CREATE CAST (character varying AS artifact_storage) WITH INOUT AS ASSIGNMENT;
+
+CREATE TABLE IF NOT EXISTS job
+(
+ uuid UUID NOT NULL
+ CONSTRAINT job_pk PRIMARY KEY,
+ secret VARCHAR(255) NOT NULL,
+ remote_id TEXT,
+ status job_status NOT NULL,
+ started_at TIMESTAMP,
+ finished_at TIMESTAMP,
+ callback_event TEXT,
+ callback_artifact TEXT,
+ train_uri TEXT,
+ created_at TIMESTAMP NOT NULL,
+ updated_at TIMESTAMP NOT NULL,
+ version BIGINT NOT NULL
+);
+
+CREATE TABLE IF NOT EXISTS job_event
+(
+ uuid UUID NOT NULL
+ CONSTRAINT job_event_pk PRIMARY KEY,
+ result_status job_status,
+ occurred_at TIMESTAMP,
+ message TEXT NOT NULL,
+ job_id UUID NOT NULL,
+ delivered BOOLEAN NOT NULL DEFAULT FALSE,
+ last_error TEXT,
+ next_dispatch_at TIMESTAMP,
+ tries INT NOT NULL DEFAULT 0,
+ created_at TIMESTAMP NOT NULL,
+ updated_at TIMESTAMP NOT NULL
+);
+
+ALTER TABLE ONLY job_event
+ ADD CONSTRAINT job_event_job_fk FOREIGN KEY (job_id) REFERENCES job (uuid);
+
+CREATE TABLE IF NOT EXISTS job_artifact
+(
+ uuid UUID NOT NULL
+ CONSTRAINT job_artifact_pk PRIMARY KEY,
+ display_name VARCHAR NOT NULL,
+ filename VARCHAR NOT NULL,
+ bytesize BIGINT NOT NULL,
+ hash VARCHAR(64) NOT NULL,
+ content_type VARCHAR NOT NULL,
+ storage artifact_storage NOT NULL,
+ occurred_at TIMESTAMP NOT NULL,
+ data BYTEA,
+ delivered BOOLEAN NOT NULL DEFAULT FALSE,
+ last_error TEXT,
+ next_dispatch_at TIMESTAMP,
+ tries INT NOT NULL DEFAULT 0,
+ job_id UUID NOT NULL,
+ created_at TIMESTAMP NOT NULL,
+ updated_at TIMESTAMP NOT NULL
+);
+
+ALTER TABLE ONLY job_artifact
+ ADD CONSTRAINT job_artifact_job_fk FOREIGN KEY (job_id) REFERENCES job (uuid);
diff --git a/src/main/resources/db/migration/V0002.0__event-delivery.sql b/src/main/resources/db/migration/V0002.0__event-delivery.sql
new file mode 100644
index 0000000..7576502
--- /dev/null
+++ b/src/main/resources/db/migration/V0002.0__event-delivery.sql
@@ -0,0 +1,21 @@
+CREATE TABLE IF NOT EXISTS event_delivery
+(
+ uuid UUID NOT NULL
+ CONSTRAINT event_delivery_pk PRIMARY KEY,
+ delivered BOOLEAN NOT NULL DEFAULT FALSE,
+ message TEXT NOT NULL,
+ dispatch_at TIMESTAMP NOT NULL,
+ dispatched_at TIMESTAMP,
+ retry_number INT NOT NULL DEFAULT 0,
+ priority INT NOT NULL DEFAULT 0,
+ job_artifact_id UUID,
+ job_event_id UUID,
+ created_at TIMESTAMP NOT NULL,
+ updated_at TIMESTAMP NOT NULL
+);
+
+ALTER TABLE ONLY event_delivery
+ ADD CONSTRAINT event_delivery_job_artifact_fk FOREIGN KEY (job_artifact_id) REFERENCES job_artifact (uuid);
+
+ALTER TABLE ONLY event_delivery
+ ADD CONSTRAINT event_delivery_job_event_fk FOREIGN KEY (job_event_id) REFERENCES job_event (uuid);
diff --git a/src/main/resources/dev/db/migration/V0001.1__dev_data.sql b/src/main/resources/dev/db/migration/V0001.1__dev_data.sql
new file mode 100644
index 0000000..fd66bfa
--- /dev/null
+++ b/src/main/resources/dev/db/migration/V0001.1__dev_data.sql
@@ -0,0 +1,38 @@
+INSERT INTO public.job (uuid, secret, remote_id, status, started_at, finished_at,
+ callback_event, callback_artifact, train_uri,
+ created_at, updated_at, version)
+VALUES ('633879bd-df36-4c93-b455-6b9a56321771', 'verySecret', 'someRemoteId', 'FINISHED', '2022-04-09 21:10:43.000000',
+ '2022-04-09 22:03:47.000000', 'https://example.com/events', 'https://example.com/artifacts',
+ 'https://example.com/trains/x', '2022-04-09 21:09:07.000000', '2022-04-09 21:09:07.000000', 0);
+INSERT INTO public.job (uuid, secret, remote_id, status, started_at, finished_at,
+ callback_event, callback_artifact, train_uri,
+ created_at, updated_at, version)
+VALUES ('0f8fa3ca-02b6-4cd3-b346-93b156166554', 'anotherSecret', 'anotherRemoteId', 'FINISHED',
+ '2022-04-09 21:15:40.000000', '2022-04-09 21:56:44.000000', 'https://example.com/events',
+ 'https://example.com/artifacts', 'https://example.com/trains/y', '2022-04-09 21:09:56.000000',
+ '2022-04-09 21:09:56.000000', 7);
+
+INSERT INTO public.job_event (uuid, result_status, occurred_at, message, job_id, created_at, updated_at)
+VALUES ('0fc29c4a-f099-46a7-8d66-90cf76eef59b', 'QUEUED', '2022-04-09 21:18:36.000000',
+ 'Train queued in the data station', '633879bd-df36-4c93-b455-6b9a56321771', '2022-04-09 21:19:01.000000',
+ '2022-04-09 21:19:01.000000');
+INSERT INTO public.job_event (uuid, result_status, occurred_at, message, job_id, created_at, updated_at)
+VALUES ('e035028e-57ab-4baa-adff-2aa5e1fb04c2', 'RUNNING', '2022-04-09 21:20:00.000000',
+ 'Started processing the train', '633879bd-df36-4c93-b455-6b9a56321771', '2022-04-09 21:20:27.000000',
+ '2022-04-09 21:20:27.000000');
+INSERT INTO public.job_event (uuid, result_status, occurred_at, message, job_id, created_at, updated_at)
+VALUES ('4fe592ca-b36a-46e5-8f3b-c7b169eb0269', null, '2022-04-09 21:20:52.000000',
+ 'Checking data access permissions', '633879bd-df36-4c93-b455-6b9a56321771', '2022-04-09 21:21:17.000000',
+ '2022-04-09 21:21:17.000000');
+INSERT INTO public.job_event (uuid, result_status, occurred_at, message, job_id, created_at, updated_at)
+VALUES ('0c6f3cca-f1a7-46db-874b-955cdd3abccc', null, '2022-04-09 21:21:53.000000',
+ 'Access granted, querying data', '633879bd-df36-4c93-b455-6b9a56321771', '2022-04-09 21:22:17.000000',
+ '2022-04-09 21:22:17.000000');
+INSERT INTO public.job_event (uuid, result_status, occurred_at, message, job_id, created_at, updated_at)
+VALUES ('80b6bfbb-d128-4af2-ac90-849b3574735b', 'FINISHED', '2022-04-09 21:22:49.000000',
+ 'Query executed successfully', '633879bd-df36-4c93-b455-6b9a56321771', '2022-04-09 21:23:08.000000',
+ '2022-04-09 21:23:08.000000');
+
+INSERT INTO public.job_artifact (uuid, display_name, filename, bytesize, hash, content_type, storage, occurred_at, data, job_id, created_at, updated_at)
+VALUES ('c667d5f3-6f34-490f-98c6-5890ddf5e456', 'Hello World Document', 'hello-world.txt', 14, 'd9014c4624844aa5bac314773d6b689ad467fa4e1d1a50a1b8a99d5a95f72ff5', 'text/plain', 'POSTGRES', '2022-04-09 21:22:00.000000', 'Hello, world!
+', '633879bd-df36-4c93-b455-6b9a56321771', '2022-04-09 21:23:00.000000', '2022-04-04 21:23:00.000000');
diff --git a/src/main/resources/log4j2.xml b/src/main/resources/log4j2.xml
new file mode 100644
index 0000000..54d9a89
--- /dev/null
+++ b/src/main/resources/log4j2.xml
@@ -0,0 +1,36 @@
+
+
+
+
+
+
+
+
+
+
+
+
+ %d{ISO8601}{GMT+0} %-6level [%t][traceId:%mdc{traceId}] %logger{1.}: %msg%n%throwable
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
diff --git a/src/test/java/org/fairdatatrain/fairdatastation/acceptance/WebIntegrationTest.java b/src/test/java/org/fairdatatrain/fairdatastation/acceptance/WebIntegrationTest.java
new file mode 100644
index 0000000..ae8a93a
--- /dev/null
+++ b/src/test/java/org/fairdatatrain/fairdatastation/acceptance/WebIntegrationTest.java
@@ -0,0 +1,45 @@
+/**
+ * The MIT License
+ * Copyright © 2022 FAIR Data Team
+ *
+ * Permission is hereby granted, free of charge, to any person obtaining a copy
+ * of this software and associated documentation files (the "Software"), to deal
+ * in the Software without restriction, including without limitation the rights
+ * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
+ * copies of the Software, and to permit persons to whom the Software is
+ * furnished to do so, subject to the following conditions:
+ *
+ * The above copyright notice and this permission notice shall be included in
+ * all copies or substantial portions of the Software.
+ *
+ * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
+ * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+ * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
+ * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
+ * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
+ * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
+ * THE SOFTWARE.
+ */
+package org.fairdatatrain.fairdatastation.acceptance;
+
+import org.junit.jupiter.api.extension.ExtendWith;
+import org.springframework.beans.factory.annotation.Autowired;
+import org.springframework.boot.test.autoconfigure.web.servlet.AutoConfigureMockMvc;
+import org.springframework.boot.test.context.SpringBootTest;
+import org.springframework.boot.test.web.client.TestRestTemplate;
+import org.springframework.test.context.ActiveProfiles;
+import org.springframework.test.context.junit.jupiter.SpringExtension;
+
+@ExtendWith(SpringExtension.class)
+@ActiveProfiles("test")
+@SpringBootTest(
+ webEnvironment = SpringBootTest.WebEnvironment.DEFINED_PORT,
+ properties = {"spring.main.allow-bean-definition-overriding=true"}
+)
+@AutoConfigureMockMvc
+public abstract class WebIntegrationTest {
+
+ @Autowired
+ protected TestRestTemplate client;
+
+}
diff --git a/src/test/java/org/fairdatatrain/fairdatastation/acceptance/actuator/ActuatorInfoDTO.java b/src/test/java/org/fairdatatrain/fairdatastation/acceptance/actuator/ActuatorInfoDTO.java
new file mode 100644
index 0000000..537b83f
--- /dev/null
+++ b/src/test/java/org/fairdatatrain/fairdatastation/acceptance/actuator/ActuatorInfoDTO.java
@@ -0,0 +1,51 @@
+/**
+ * The MIT License
+ * Copyright © 2022 FAIR Data Team
+ *
+ * Permission is hereby granted, free of charge, to any person obtaining a copy
+ * of this software and associated documentation files (the "Software"), to deal
+ * in the Software without restriction, including without limitation the rights
+ * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
+ * copies of the Software, and to permit persons to whom the Software is
+ * furnished to do so, subject to the following conditions:
+ *
+ * The above copyright notice and this permission notice shall be included in
+ * all copies or substantial portions of the Software.
+ *
+ * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
+ * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+ * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
+ * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
+ * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
+ * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
+ * THE SOFTWARE.
+ */
+package org.fairdatatrain.fairdatastation.acceptance.actuator;
+
+import com.fasterxml.jackson.annotation.JsonIgnoreProperties;
+import jakarta.validation.constraints.NotBlank;
+import jakarta.validation.constraints.NotNull;
+import lombok.AllArgsConstructor;
+import lombok.Getter;
+import lombok.NoArgsConstructor;
+import lombok.Setter;
+
+@NoArgsConstructor
+@AllArgsConstructor
+@Getter
+@Setter
+@JsonIgnoreProperties(ignoreUnknown = true)
+public class ActuatorInfoDTO {
+
+ @NotNull
+ @NotBlank
+ private String name;
+
+ @NotNull
+ @NotBlank
+ private String version;
+
+ @NotNull
+ @NotBlank
+ private String builtAt;
+}
diff --git a/src/test/java/org/fairdatatrain/fairdatastation/acceptance/actuator/List_Info_GET.java b/src/test/java/org/fairdatatrain/fairdatastation/acceptance/actuator/List_Info_GET.java
new file mode 100644
index 0000000..602fc23
--- /dev/null
+++ b/src/test/java/org/fairdatatrain/fairdatastation/acceptance/actuator/List_Info_GET.java
@@ -0,0 +1,82 @@
+/**
+ * The MIT License
+ * Copyright © 2022 FAIR Data Team
+ *
+ * Permission is hereby granted, free of charge, to any person obtaining a copy
+ * of this software and associated documentation files (the "Software"), to deal
+ * in the Software without restriction, including without limitation the rights
+ * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
+ * copies of the Software, and to permit persons to whom the Software is
+ * furnished to do so, subject to the following conditions:
+ *
+ * The above copyright notice and this permission notice shall be included in
+ * all copies or substantial portions of the Software.
+ *
+ * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
+ * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+ * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
+ * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
+ * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
+ * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
+ * THE SOFTWARE.
+ */
+package org.fairdatatrain.fairdatastation.acceptance.actuator;
+
+import org.fairdatatrain.fairdatastation.acceptance.WebIntegrationTest;
+import org.junit.jupiter.api.DisplayName;
+import org.junit.jupiter.api.Test;
+import org.springframework.beans.factory.annotation.Value;
+import org.springframework.core.ParameterizedTypeReference;
+import org.springframework.http.HttpStatus;
+import org.springframework.http.RequestEntity;
+import org.springframework.http.ResponseEntity;
+
+import java.net.URI;
+
+import static java.lang.String.format;
+import static org.hamcrest.MatcherAssert.assertThat;
+import static org.hamcrest.Matchers.*;
+
+@DisplayName("GET /actuator/info")
+public class List_Info_GET extends WebIntegrationTest {
+
+ @Value("${git.branch}")
+ private String branch;
+
+ @Value("${git.commit.id.abbrev}")
+ private String commitShort;
+
+ @Value("${git.tags}")
+ private String tag;
+
+ @Value("${build.time}")
+ private String buildTime;
+
+ private URI url() {
+ return URI.create("/actuator/info");
+ }
+
+ @Test
+ public void res200() {
+ // GIVEN:
+ RequestEntity request = RequestEntity
+ .get(url())
+ .build();
+ ParameterizedTypeReference responseType = new ParameterizedTypeReference<>() {
+ };
+
+ // WHEN:
+ ResponseEntity result = client.exchange(request, responseType);
+
+ // THEN:
+ assertThat(result.getStatusCode(), is(equalTo(HttpStatus.OK)));
+ assertThat(result.getBody(), is(notNullValue()));
+ if (tag == null || tag.isEmpty()) {
+ assertThat(result.getBody().getVersion(), is(equalTo((format("%s~%s", branch, commitShort)))));
+ } else {
+ assertThat(result.getBody().getVersion(), is(equalTo((format("%s~%s", tag, commitShort)))));
+ }
+ assertThat(result.getBody().getBuiltAt(), is(equalTo(buildTime)));
+ }
+
+}