diff --git a/.dockerignore b/.dockerignore
new file mode 100644
index 0000000..814842b
--- /dev/null
+++ b/.dockerignore
@@ -0,0 +1,11 @@
+.github
+.idea
+LICENSE
+CHANGELOG.md
+CONTRIBUTING.md
+README.md
+SECURITY.md
+target/**
+!target/fdp-spring-boot.jar
+!target/classes/application-production.yml
+nb-configuration.xml
diff --git a/.github/workflows/build.yml b/.github/workflows/build.yml
new file mode 100644
index 0000000..016b37f
--- /dev/null
+++ b/.github/workflows/build.yml
@@ -0,0 +1,165 @@
+name: "Build"
+
+on:
+ push:
+ pull_request:
+
+jobs:
+
+ test:
+ name: Maven Test & Package
+ runs-on: ubuntu-latest
+
+ env:
+ JAVA_DISTRIBUTION: temurin
+ JAVA_VERSION: 17
+
+ services:
+ postgres:
+ image: postgres:15.1
+ ports:
+ - 5432:5432
+ env:
+ POSTGRES_DB: train_handler_test
+ POSTGRES_USER: postgres
+ POSTGRES_PASSWORD: password
+ # Set health checks to wait until postgres has started
+ options: >-
+ --health-cmd pg_isready
+ --health-interval 10s
+ --health-timeout 5s
+ --health-retries 5
+
+ 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/trainhandler-server
+ PRIVATE_IMAGE: ${{ secrets.PRIVATE_REGISTRY_URL }}/trainhandler-server
+ 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..56063c5
--- /dev/null
+++ b/.github/workflows/code-style.yml
@@ -0,0 +1,88 @@
+name: "Code Style"
+
+on:
+ push:
+ pull_request:
+
+jobs:
+
+ checkstyle:
+ name: Checkstyle
+ runs-on: ubuntu-latest
+
+ env:
+ JAVA_DISTRIBUTION: temurin
+ JAVA_VERSION: 17
+
+ steps:
+ - name: Checkout repository
+ uses: actions/checkout@v3
+
+ - 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 Checkstyle
+ run: |
+ mvn -B checkstyle:checkstyle
+
+ spotbugs:
+ name: SpotBugs
+ runs-on: ubuntu-latest
+
+ env:
+ JAVA_DISTRIBUTION: temurin
+ JAVA_VERSION: 17
+
+ steps:
+ - name: Checkout repository
+ uses: actions/checkout@v3
+
+ - 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 SpotBugs
+ run: |
+ mvn -B spotbugs:check
+
+ license-head:
+ name: License Headers
+ runs-on: ubuntu-latest
+
+ env:
+ JAVA_DISTRIBUTION: temurin
+ JAVA_VERSION: 17
+
+ steps:
+ - name: Checkout repository
+ uses: actions/checkout@v3
+
+ - 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: 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..6d6e355
--- /dev/null
+++ b/.github/workflows/security.yml
@@ -0,0 +1,102 @@
+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
+
+ env:
+ JAVA_DISTRIBUTION: temurin
+ JAVA_VERSION: 17
+
+ steps:
+ - name: Checkout repository
+ uses: actions/checkout@v2
+ with:
+ fetch-depth: 0
+
+ - name: Setup Java
+ uses: actions/setup-java@v2
+ with:
+ distribution: ${{ env.JAVA_DISTRIBUTION }}
+ java-version: ${{ env.JAVA_VERSION }}
+ 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@master
+
+ - 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
+
+ env:
+ PUBLIC_IMAGE: fairdata/trainhandler-server
+ TAG_DEVELOP: develop
+
+ steps:
+
+ - name: Checkout repository
+ uses: actions/checkout@master
+
+ - name: Docker build
+ run: |
+ docker pull $PUBLIC_IMAGE:$TAG_DEVELOP
+ docker build --cache-from $PUBLIC_IMAGE:$TAG_DEVELOP -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 19d61e9..f94f153 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 Train Handler server project
-
+- Setup CI, code style checks, and security audits
[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 2319cea..d545786 100644
--- a/README.md
+++ b/README.md
@@ -4,11 +4,65 @@
## Usage
-*To be done*
+Train Handler is intended to be used together with [client](https://github.com/FAIRDataTeam/TrainHandler-client) via Docker (unless for development purposes).
+
+The intended use is via Docker and Docker Compose, configured via envvars:
+
+```yaml
+ trainhandler-server:
+ image: fairdata/trainhandler-server:latest
+ restart: unless-stopped
+ # volumes:
+ # - ${PROJECT_ROOT}/application.yml:/app/application.yml:ro
+ environment:
+ FDT_DISPATCH_ROOT: ${API_URL}
+ FDT_DISPATCH_INTERVAL: PT60S
+ FDT_POSTGRES_DB: ${POSTGRES_DB}
+ FDT_POSTGRES_USERNAME: ${POSTGRES_USER}
+ FDT_POSTGRES_PASSWORD: ${POSTGRES_PASSWORD}
+ FDT_KEYCLOAK_ENABLED: true
+ FDT_KEYCLOAK_URL: ${KEYCLOAK_URL}
+ FDT_KEYCLOAK_REALM: ${KEYCLOAK_REALM}
+ FDT_KEYCLOAK_RESOURCE: ${KEYCLOAK_CLIENT_API}
+```
## Development
-*To be done*
+### Build & Run
+
+To run the application, a PostgreSQL database is required to be running. To configure the MongoDB with standard
+connection (`postgresql://localhost:5432/train-handler`), simply instruct Spring Boot to use the `dev` profile. Then run:
+
+```bash
+$ mvn spring-boot:run -Dspring-boot.run.profiles=dev
+```
+
+Alternatively, set the `dev` profile in your IDE that is used to launch the application.
+
+### 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
+
+You do not have to install Java and IDE locally, we supply multistage Dockerfile that first
+build `jar` file and then creates the image for deploying Train Handler:
+
+```bash
+$ docker build -t trainhandler-server:local .
+```
## Contributing
diff --git a/checkstyle.xml b/checkstyle.xml
new file mode 100644
index 0000000..3bdffe8
--- /dev/null
+++ b/checkstyle.xml
@@ -0,0 +1,784 @@
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
diff --git a/pom.xml b/pom.xml
new file mode 100644
index 0000000..261b696
--- /dev/null
+++ b/pom.xml
@@ -0,0 +1,329 @@
+
+
+ 4.0.0
+
+ org.springframework.boot
+ spring-boot-starter-parent
+ 2.7.5
+
+
+
+ org.fairdatatrain
+ trainhandler
+ 0.1.0
+
+ Train Handler
+ FAIR Data Train Handler
+ 2022
+
+
+ The MIT License
+ https://opensource.org/licenses/MIT
+
+
+
+
+
+ Marek Suchánek
+ https://github.com/MarekSuchanek
+
+
+
+
+ 17
+
+
+ 1.6.14
+ 42.5.4
+ 1.18.26
+ 2.14.1
+ 4.2.3
+ 21.0.1
+
+
+ 4.1
+ 3.2.1
+ 4.7.3.2
+ 5.0.0
+ 0.2.0
+
+
+
+
+ org.springframework.boot
+ spring-boot-starter-web
+
+
+ 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.springframework.boot
+ spring-boot-configuration-processor
+
+
+ org.springframework.boot
+ spring-boot-starter-webflux
+
+
+ org.springframework.boot
+ spring-boot-starter-security
+
+
+
+ org.flywaydb
+ flyway-core
+
+
+ org.projectlombok
+ lombok
+ ${lombok.version}
+
+
+ org.springdoc
+ springdoc-openapi-ui
+ ${springdoc.version}
+
+
+
+ org.postgresql
+ postgresql
+ ${postgresql.version}
+ runtime
+
+
+
+ org.springframework.boot
+ spring-boot-starter-test
+ test
+
+
+ org.springframework.security
+ spring-security-test
+ test
+
+
+
+ org.eclipse.rdf4j
+ rdf4j-runtime
+ ${rdf4j.version}
+ pom
+
+
+ ch.qos.logback
+ logback-classic
+
+
+
+
+ org.eclipse.rdf4j
+ rdf4j-rio-api
+ ${rdf4j.version}
+
+
+ org.eclipse.rdf4j
+ rdf4j-sail-nativerdf
+ ${rdf4j.version}
+
+
+
+ org.keycloak
+ keycloak-spring-boot-starter
+ ${keycloak.version}
+
+
+ org.keycloak
+ keycloak-spring-security-adapter
+ ${keycloak.version}
+
+
+
+
+
+
+ org.keycloak.bom
+ keycloak-adapter-bom
+ ${keycloak.version}
+ pom
+ import
+
+
+
+
+
+ app
+
+
+ org.springframework.boot
+ spring-boot-maven-plugin
+
+
+
+ repackage
+
+
+ spring-boot
+ nl.dtls.fairdatapoint.Application
+
+
+
+ build-info
+
+ build-info
+
+
+
+
+
+ com.mycila
+ license-maven-plugin
+ ${plugin.license.version}
+
+ com/mycila/maven/plugin/license/templates/MIT.txt
+
+ FAIR Data Team
+
+
+ JAVADOC_STYLE
+
+
+ pom.xml
+ **/maven.config
+ **/*.ttl
+ **/*.yml
+ **/*.sparql
+ **/*.properties
+ **/*.xml
+ **/*.json
+ LICENSE
+ .gitignore
+ .dockerignore
+
+
+
+
+ process-sources
+
+ format
+
+
+
+
+
+ org.apache.maven.plugins
+ maven-checkstyle-plugin
+ ${plugin.checkstyle.version}
+
+ true
+ true
+ checkstyle.xml
+
+
+
+ com.puppycrawl.tools
+ checkstyle
+ 10.7.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/FDP-O/v1.1/fdp-ontology.owl
+ https://w3id.org/fdp/fdp-o#
+ fdp
+
+
+ https://raw.githubusercontent.com/FAIRDataTeam/FDT-O/778dcc29d54608fc942eb6b3260e9667a6227edc/fdt-ontology.owl
+ https://w3id.org/fdt/fdt-o#
+ fdt
+
+
+
+
+
+
+
+
+ spring-milestones
+ Spring Milestones
+ https://repo.spring.io/milestone
+
+ false
+
+
+
+
+
+ spring-milestones
+ Spring Milestones
+ https://repo.spring.io/milestone
+
+ false
+
+
+
+
+
diff --git a/src/main/java/org/fairdatatrain/trainhandler/Application.java b/src/main/java/org/fairdatatrain/trainhandler/Application.java
new file mode 100644
index 0000000..93b2411
--- /dev/null
+++ b/src/main/java/org/fairdatatrain/trainhandler/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.trainhandler;
+
+import org.springframework.boot.SpringApplication;
+import org.springframework.boot.autoconfigure.SpringBootApplication;
+import org.springframework.context.annotation.ComponentScan;
+import org.springframework.scheduling.annotation.EnableAsync;
+import org.springframework.scheduling.annotation.EnableScheduling;
+import org.springframework.web.servlet.config.annotation.EnableWebMvc;
+
+@SpringBootApplication
+@EnableAsync
+@EnableScheduling
+@EnableWebMvc
+@ComponentScan(basePackages = "org.fairdatatrain.trainhandler.*")
+public class Application {
+
+ public static void main(String[] args) {
+ SpringApplication.run(Application.class, args);
+ }
+
+}
diff --git a/src/main/java/org/fairdatatrain/trainhandler/api/controller/ApiExceptionHandler.java b/src/main/java/org/fairdatatrain/trainhandler/api/controller/ApiExceptionHandler.java
new file mode 100644
index 0000000..680ffea
--- /dev/null
+++ b/src/main/java/org/fairdatatrain/trainhandler/api/controller/ApiExceptionHandler.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.trainhandler.api.controller;
+
+import org.fairdatatrain.trainhandler.api.dto.error.ErrorDTO;
+import org.fairdatatrain.trainhandler.exception.CannotPerformException;
+import org.fairdatatrain.trainhandler.exception.NotFoundException;
+import org.fairdatatrain.trainhandler.exception.NotImplementedException;
+import org.springframework.http.HttpStatus;
+import org.springframework.http.ResponseEntity;
+import org.springframework.web.bind.annotation.ControllerAdvice;
+import org.springframework.web.bind.annotation.ExceptionHandler;
+
+import javax.validation.ValidationException;
+
+import static java.lang.String.format;
+
+@ControllerAdvice
+public class ApiExceptionHandler {
+
+ @ExceptionHandler(NotImplementedException.class)
+ public ResponseEntity handleNotImplementedException(
+ NotImplementedException exception) {
+ return new ResponseEntity<>(
+ new ErrorDTO("HTTP-501", "Not yet implemented."), HttpStatus.NOT_IMPLEMENTED);
+ }
+
+ @ExceptionHandler(NotFoundException.class)
+ public ResponseEntity handleNotFoundException(NotFoundException exception) {
+ return new ResponseEntity<>(
+ new ErrorDTO(
+ "HTTP-404",
+ format(
+ "Cannot find entity %s with %s",
+ exception.getEntityName(), exception.getFields()
+ )
+ ),
+ HttpStatus.NOT_FOUND
+ );
+ }
+
+ @ExceptionHandler(CannotPerformException.class)
+ public ResponseEntity handleCannotDeleteException(CannotPerformException exception) {
+ return new ResponseEntity<>(
+ new ErrorDTO(
+ "HTTP-400-DeletionError",
+ format(
+ "Cannot perform %s on entity %s (with %s)",
+ exception.getOperation(),
+ exception.getEntityName(),
+ exception.getFields()
+ )
+ ),
+ HttpStatus.BAD_REQUEST
+ );
+ }
+
+ @ExceptionHandler(ValidationException.class)
+ public ResponseEntity handleValidationException(ValidationException exception) {
+ return new ResponseEntity<>(
+ new ErrorDTO(
+ "HTTP-400-ValidationError",
+ exception.getMessage()
+ ),
+ HttpStatus.BAD_REQUEST
+ );
+ }
+}
diff --git a/src/main/java/org/fairdatatrain/trainhandler/api/controller/JobArtifactController.java b/src/main/java/org/fairdatatrain/trainhandler/api/controller/JobArtifactController.java
new file mode 100644
index 0000000..c7e69f1
--- /dev/null
+++ b/src/main/java/org/fairdatatrain/trainhandler/api/controller/JobArtifactController.java
@@ -0,0 +1,106 @@
+/**
+ * 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.trainhandler.api.controller;
+
+import io.swagger.v3.oas.annotations.tags.Tag;
+import lombok.RequiredArgsConstructor;
+import org.fairdatatrain.trainhandler.api.dto.job.*;
+import org.fairdatatrain.trainhandler.data.model.JobArtifact;
+import org.fairdatatrain.trainhandler.exception.JobSecurityException;
+import org.fairdatatrain.trainhandler.exception.NotFoundException;
+import org.fairdatatrain.trainhandler.service.job.artifact.JobArtifactService;
+import org.fairdatatrain.trainhandler.service.job.event.JobEventService;
+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.security.access.prepost.PreAuthorize;
+import org.springframework.web.bind.annotation.*;
+
+import javax.validation.Valid;
+import java.util.List;
+import java.util.UUID;
+
+import static java.lang.String.format;
+
+@Tag(name = "Runs")
+@PreAuthorize("hasRole('user')")
+@RestController
+@RequestMapping("/runs")
+@RequiredArgsConstructor
+public class JobArtifactController {
+
+ public static final String ARTIFACT_CALLBACK_LOCATION = "/{runUuid}/jobs/{jobUuid}/artifacts";
+
+ private final JobArtifactService jobArtifactService;
+
+ private final JobEventService jobEventService;
+
+ @GetMapping(
+ path = "/{runUuid}/jobs/{jobUuid}/artifacts",
+ produces = MediaType.APPLICATION_JSON_VALUE
+ )
+ public List getJobArtifacts(
+ @PathVariable UUID runUuid, @PathVariable UUID jobUuid
+ ) throws NotFoundException {
+ return jobArtifactService.getArtifacts(runUuid, jobUuid);
+ }
+
+ @GetMapping(
+ path = "/{runUuid}/jobs/{jobUuid}/artifacts/{artifactUuid}/download"
+ )
+ public ResponseEntity getJobArtifactData(
+ @PathVariable UUID runUuid,
+ @PathVariable UUID jobUuid,
+ @PathVariable UUID artifactUuid
+ ) throws NotFoundException {
+ final JobArtifact artifact = jobArtifactService.getArtifact(runUuid, 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);
+ }
+
+ @PostMapping(
+ path = ARTIFACT_CALLBACK_LOCATION,
+ consumes = MediaType.APPLICATION_JSON_VALUE,
+ produces = MediaType.APPLICATION_JSON_VALUE
+ )
+ public JobArtifactDTO addJobArtifact(
+ @PathVariable UUID runUuid,
+ @PathVariable UUID jobUuid,
+ @RequestBody @Valid JobArtifactCreateDTO reqDto
+ ) throws NotFoundException, JobSecurityException {
+ final JobArtifactDTO dto = jobArtifactService.createArtifact(runUuid, jobUuid, reqDto);
+ jobEventService.notify(jobUuid);
+ return dto;
+ }
+}
diff --git a/src/main/java/org/fairdatatrain/trainhandler/api/controller/JobController.java b/src/main/java/org/fairdatatrain/trainhandler/api/controller/JobController.java
new file mode 100644
index 0000000..6682ebb
--- /dev/null
+++ b/src/main/java/org/fairdatatrain/trainhandler/api/controller/JobController.java
@@ -0,0 +1,72 @@
+/**
+ * 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.trainhandler.api.controller;
+
+import io.swagger.v3.oas.annotations.tags.Tag;
+import lombok.RequiredArgsConstructor;
+import org.fairdatatrain.trainhandler.api.dto.job.*;
+import org.fairdatatrain.trainhandler.config.DispatcherConfig;
+import org.fairdatatrain.trainhandler.exception.NotFoundException;
+import org.fairdatatrain.trainhandler.service.job.JobService;
+import org.springframework.data.domain.Page;
+import org.springframework.data.domain.Pageable;
+import org.springframework.http.MediaType;
+import org.springframework.security.access.prepost.PreAuthorize;
+import org.springframework.web.bind.annotation.*;
+import org.springframework.web.context.request.async.DeferredResult;
+
+import java.util.UUID;
+
+@Tag(name = "Runs")
+@PreAuthorize("hasRole('user')")
+@RestController
+@RequestMapping("/runs")
+@RequiredArgsConstructor
+public class JobController {
+
+ private final JobService jobService;
+
+ private final DispatcherConfig config;
+
+ @GetMapping(path = "/{runUuid}/jobs", produces = MediaType.APPLICATION_JSON_VALUE)
+ public Page getJobs(@PathVariable UUID runUuid, Pageable pageable) {
+ return jobService.getJobsForRun(runUuid, pageable);
+ }
+
+ @GetMapping(
+ path = "/{runUuid}/jobs/{jobUuid}",
+ produces = MediaType.APPLICATION_JSON_VALUE
+ )
+ public DeferredResult pollJob(
+ @PathVariable UUID runUuid,
+ @PathVariable UUID jobUuid,
+ @RequestParam(required = false, defaultValue = "0") Long after
+ ) throws NotFoundException {
+ final JobDTO currentJob = jobService.getSingle(runUuid, jobUuid);
+ final DeferredResult result = new DeferredResult<>(
+ config.getPolling().getTimeoutMs(), currentJob
+ );
+ jobService.poll(jobUuid, result, after, currentJob);
+ return result;
+ }
+}
diff --git a/src/main/java/org/fairdatatrain/trainhandler/api/controller/JobEventController.java b/src/main/java/org/fairdatatrain/trainhandler/api/controller/JobEventController.java
new file mode 100644
index 0000000..20a0654
--- /dev/null
+++ b/src/main/java/org/fairdatatrain/trainhandler/api/controller/JobEventController.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.trainhandler.api.controller;
+
+import io.swagger.v3.oas.annotations.tags.Tag;
+import lombok.RequiredArgsConstructor;
+import org.fairdatatrain.trainhandler.api.dto.job.*;
+import org.fairdatatrain.trainhandler.exception.JobSecurityException;
+import org.fairdatatrain.trainhandler.exception.NotFoundException;
+import org.fairdatatrain.trainhandler.service.job.event.JobEventService;
+import org.springframework.http.MediaType;
+import org.springframework.security.access.prepost.PreAuthorize;
+import org.springframework.web.bind.annotation.*;
+
+import javax.validation.Valid;
+import java.util.List;
+import java.util.UUID;
+
+@Tag(name = "Runs")
+@PreAuthorize("hasRole('user')")
+@RestController
+@RequestMapping("/runs")
+@RequiredArgsConstructor
+public class JobEventController {
+
+ public static final String EVENT_CALLBACK_LOCATION = "/{runUuid}/jobs/{jobUuid}/events";
+
+ private final JobEventService jobEventService;
+
+ @GetMapping(
+ path = "/{runUuid}/jobs/{jobUuid}/events",
+ produces = MediaType.APPLICATION_JSON_VALUE
+ )
+ public List getJobEvents(
+ @PathVariable UUID runUuid,
+ @PathVariable UUID jobUuid
+ ) throws NotFoundException {
+ return jobEventService.getEvents(runUuid, jobUuid);
+ }
+
+ @PostMapping(
+ path = EVENT_CALLBACK_LOCATION,
+ consumes = MediaType.APPLICATION_JSON_VALUE,
+ produces = MediaType.APPLICATION_JSON_VALUE
+ )
+ public JobEventDTO addJobEvent(
+ @PathVariable UUID runUuid,
+ @PathVariable UUID jobUuid,
+ @RequestBody @Valid JobEventCreateDTO reqDto
+ ) throws NotFoundException, JobSecurityException {
+ final JobEventDTO dto = jobEventService.createEvent(runUuid, jobUuid, reqDto);
+ jobEventService.notify(jobUuid);
+ return dto;
+ }
+}
diff --git a/src/main/java/org/fairdatatrain/trainhandler/api/controller/PlanController.java b/src/main/java/org/fairdatatrain/trainhandler/api/controller/PlanController.java
new file mode 100644
index 0000000..a1870b3
--- /dev/null
+++ b/src/main/java/org/fairdatatrain/trainhandler/api/controller/PlanController.java
@@ -0,0 +1,98 @@
+/**
+ * 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.trainhandler.api.controller;
+
+import io.swagger.v3.oas.annotations.tags.Tag;
+import lombok.RequiredArgsConstructor;
+import org.fairdatatrain.trainhandler.api.dto.plan.PlanCreateDTO;
+import org.fairdatatrain.trainhandler.api.dto.plan.PlanDTO;
+import org.fairdatatrain.trainhandler.api.dto.plan.PlanSimpleDTO;
+import org.fairdatatrain.trainhandler.api.dto.plan.PlanUpdateDTO;
+import org.fairdatatrain.trainhandler.api.dto.run.RunSimpleDTO;
+import org.fairdatatrain.trainhandler.exception.CannotPerformException;
+import org.fairdatatrain.trainhandler.exception.NotFoundException;
+import org.fairdatatrain.trainhandler.service.plan.PlanService;
+import org.fairdatatrain.trainhandler.service.run.RunService;
+import org.springframework.data.domain.Page;
+import org.springframework.data.domain.Pageable;
+import org.springframework.data.web.PageableDefault;
+import org.springframework.http.HttpStatus;
+import org.springframework.http.MediaType;
+import org.springframework.security.access.prepost.PreAuthorize;
+import org.springframework.web.bind.annotation.*;
+
+import javax.validation.Valid;
+import java.util.UUID;
+
+@Tag(name = "Plans")
+@PreAuthorize("hasRole('user')")
+@RestController
+@RequestMapping("/plans")
+@RequiredArgsConstructor
+public class PlanController {
+
+ private final PlanService planService;
+
+ private final RunService runService;
+
+ @GetMapping(path = "", produces = MediaType.APPLICATION_JSON_VALUE)
+ public Page getPaged(
+ @RequestParam(value = "query", required = false, defaultValue = "") String query,
+ @PageableDefault(sort = "displayName") Pageable pageable) {
+ return planService.getPaged(query, pageable);
+ }
+
+ @PostMapping(
+ path = "",
+ consumes = MediaType.APPLICATION_JSON_VALUE,
+ produces = MediaType.APPLICATION_JSON_VALUE)
+ @ResponseStatus(code = HttpStatus.CREATED)
+ public PlanDTO create(@Valid @RequestBody PlanCreateDTO reqDto) throws NotFoundException {
+ return planService.create(reqDto);
+ }
+
+ @GetMapping(path = "/{uuid}", produces = MediaType.APPLICATION_JSON_VALUE)
+ public PlanDTO getSingle(@PathVariable UUID uuid) throws NotFoundException {
+ return planService.getSingle(uuid);
+ }
+
+ @PutMapping(
+ path = "/{uuid}",
+ consumes = MediaType.APPLICATION_JSON_VALUE,
+ produces = MediaType.APPLICATION_JSON_VALUE)
+ public PlanDTO update(@PathVariable UUID uuid, @Valid @RequestBody PlanUpdateDTO reqDto)
+ throws NotFoundException, CannotPerformException {
+ return planService.update(uuid, reqDto);
+ }
+
+ @DeleteMapping(path = "/{uuid}", produces = MediaType.APPLICATION_JSON_VALUE)
+ @ResponseStatus(code = HttpStatus.NO_CONTENT)
+ public void delete(@PathVariable UUID uuid) throws NotFoundException, CannotPerformException {
+ planService.delete(uuid);
+ }
+
+ @GetMapping(path = "/{uuid}/runs", produces = MediaType.APPLICATION_JSON_VALUE)
+ public Page getPlanRuns(@PathVariable UUID uuid, Pageable pageable) {
+ return runService.getRunsForPlanUuid(uuid, pageable);
+ }
+}
diff --git a/src/main/java/org/fairdatatrain/trainhandler/api/controller/PlanLatestController.java b/src/main/java/org/fairdatatrain/trainhandler/api/controller/PlanLatestController.java
new file mode 100644
index 0000000..f6aa533
--- /dev/null
+++ b/src/main/java/org/fairdatatrain/trainhandler/api/controller/PlanLatestController.java
@@ -0,0 +1,83 @@
+/**
+ * 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.trainhandler.api.controller;
+
+import io.swagger.v3.oas.annotations.tags.Tag;
+import lombok.RequiredArgsConstructor;
+import org.fairdatatrain.trainhandler.api.dto.job.JobArtifactDTO;
+import org.fairdatatrain.trainhandler.data.model.JobArtifact;
+import org.fairdatatrain.trainhandler.exception.NotFoundException;
+import org.fairdatatrain.trainhandler.service.plan.latest.PlanLatestService;
+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.*;
+
+import java.util.List;
+import java.util.UUID;
+
+import static java.lang.String.format;
+
+@Tag(name = "Plans")
+@RestController
+@RequestMapping("/plans")
+@RequiredArgsConstructor
+public class PlanLatestController {
+
+ private final PlanLatestService planLatestService;
+
+ @GetMapping(path = "/{planUuid}/latest/artifacts", produces = MediaType.APPLICATION_JSON_VALUE)
+ public List getLatestArtifacts(@PathVariable UUID planUuid) throws NotFoundException {
+ return planLatestService.getLatestArtifacts(planUuid);
+ }
+
+ @GetMapping(path = "/{planUuid}/latest/artifacts/{artifactUuid}")
+ public ResponseEntity downloadLatestArtifact(
+ @PathVariable UUID planUuid, @PathVariable UUID artifactUuid
+ ) throws NotFoundException {
+ final JobArtifact artifact = planLatestService.getLatestJobArtifact(planUuid, artifactUuid);
+ final byte[] data = planLatestService.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);
+ }
+
+ @GetMapping(
+ path = "/{planUuid}/target/{targetUuid}/latest/artifacts",
+ produces = MediaType.APPLICATION_JSON_VALUE
+ )
+ public List getLatestArtifactsOfTarget(
+ @PathVariable UUID planUuid, @PathVariable UUID targetUuid
+ ) throws NotFoundException {
+ return planLatestService.getLatestArtifactsOfTarget(planUuid, targetUuid);
+ }
+}
diff --git a/src/main/java/org/fairdatatrain/trainhandler/api/controller/RunController.java b/src/main/java/org/fairdatatrain/trainhandler/api/controller/RunController.java
new file mode 100644
index 0000000..a7c80d5
--- /dev/null
+++ b/src/main/java/org/fairdatatrain/trainhandler/api/controller/RunController.java
@@ -0,0 +1,95 @@
+/**
+ * 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.trainhandler.api.controller;
+
+import io.swagger.v3.oas.annotations.tags.Tag;
+import lombok.RequiredArgsConstructor;
+import org.fairdatatrain.trainhandler.api.dto.run.RunCreateDTO;
+import org.fairdatatrain.trainhandler.api.dto.run.RunDTO;
+import org.fairdatatrain.trainhandler.api.dto.run.RunUpdateDTO;
+import org.fairdatatrain.trainhandler.config.DispatcherConfig;
+import org.fairdatatrain.trainhandler.exception.CannotPerformException;
+import org.fairdatatrain.trainhandler.exception.NotFoundException;
+import org.fairdatatrain.trainhandler.service.async.AsyncEventPublisher;
+import org.fairdatatrain.trainhandler.service.run.RunService;
+import org.springframework.http.HttpStatus;
+import org.springframework.http.MediaType;
+import org.springframework.security.access.prepost.PreAuthorize;
+import org.springframework.web.bind.annotation.*;
+import org.springframework.web.context.request.async.DeferredResult;
+
+import javax.validation.Valid;
+import java.util.UUID;
+
+@Tag(name = "Runs")
+@PreAuthorize("hasRole('user')")
+@RestController
+@RequestMapping("/runs")
+@RequiredArgsConstructor
+public class RunController {
+
+ private final RunService runService;
+
+ private final AsyncEventPublisher asyncEventPublisher;
+
+ private final DispatcherConfig dispatcherConfig;
+
+ @PostMapping(
+ path = "",
+ consumes = MediaType.APPLICATION_JSON_VALUE,
+ produces = MediaType.APPLICATION_JSON_VALUE
+ )
+ @ResponseStatus(code = HttpStatus.CREATED)
+ public RunDTO create(@Valid @RequestBody RunCreateDTO reqDto) throws NotFoundException {
+ return runService.create(reqDto);
+ }
+
+ @GetMapping(
+ path = "/{uuid}",
+ produces = MediaType.APPLICATION_JSON_VALUE
+ )
+ public DeferredResult pollRun(
+ @PathVariable UUID uuid,
+ @RequestParam(required = false, defaultValue = "0") Long after
+ ) throws NotFoundException {
+ final RunDTO currentRun = runService.getSingle(uuid);
+ final DeferredResult result = new DeferredResult<>(
+ dispatcherConfig.getPolling().getTimeoutMs(), currentRun
+ );
+ runService.poll(uuid, result, after, currentRun);
+ return result;
+ }
+
+ @PutMapping(
+ path = "/{uuid}",
+ consumes = MediaType.APPLICATION_JSON_VALUE,
+ produces = MediaType.APPLICATION_JSON_VALUE
+ )
+ public RunDTO update(
+ @PathVariable UUID uuid, @Valid @RequestBody RunUpdateDTO reqDto
+ ) throws NotFoundException, CannotPerformException {
+ final RunDTO run = runService.update(uuid, reqDto);
+ asyncEventPublisher.publishNewJobEventNotification(run);
+ return run;
+ }
+}
diff --git a/src/main/java/org/fairdatatrain/trainhandler/api/controller/StationController.java b/src/main/java/org/fairdatatrain/trainhandler/api/controller/StationController.java
new file mode 100644
index 0000000..ce13268
--- /dev/null
+++ b/src/main/java/org/fairdatatrain/trainhandler/api/controller/StationController.java
@@ -0,0 +1,105 @@
+/**
+ * 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.trainhandler.api.controller;
+
+import io.swagger.v3.oas.annotations.tags.Tag;
+import lombok.RequiredArgsConstructor;
+import org.fairdatatrain.trainhandler.api.dto.station.StationCreateDTO;
+import org.fairdatatrain.trainhandler.api.dto.station.StationDTO;
+import org.fairdatatrain.trainhandler.api.dto.station.StationSimpleDTO;
+import org.fairdatatrain.trainhandler.api.dto.station.StationUpdateDTO;
+import org.fairdatatrain.trainhandler.api.dto.train.TrainSimpleDTO;
+import org.fairdatatrain.trainhandler.exception.NotFoundException;
+import org.fairdatatrain.trainhandler.service.station.StationService;
+import org.springframework.data.domain.Page;
+import org.springframework.data.domain.Pageable;
+import org.springframework.data.web.PageableDefault;
+import org.springframework.http.MediaType;
+import org.springframework.security.access.prepost.PreAuthorize;
+import org.springframework.web.bind.annotation.*;
+
+import javax.validation.Valid;
+import java.util.List;
+import java.util.UUID;
+
+@Tag(name = "Stations")
+@PreAuthorize("hasRole('user')")
+@RestController
+@RequestMapping("/stations")
+@RequiredArgsConstructor
+public class StationController {
+
+ private final StationService stationService;
+
+ @GetMapping(path = "", produces = MediaType.APPLICATION_JSON_VALUE)
+ public Page getPaged(
+ @RequestParam(value = "query", required = false, defaultValue = "") String query,
+ @PageableDefault(sort = "title") Pageable pageable) {
+ return stationService.getPaged(query, pageable);
+ }
+
+ @GetMapping(path = "/all", produces = MediaType.APPLICATION_JSON_VALUE)
+ public List getPaged(
+ @RequestParam(value = "query", required = false, defaultValue = "") String query) {
+ return stationService.getAll(query);
+ }
+
+ @PostMapping(
+ path = "",
+ consumes = MediaType.APPLICATION_JSON_VALUE,
+ produces = MediaType.APPLICATION_JSON_VALUE
+ )
+ public StationDTO create(
+ @RequestBody @Valid StationCreateDTO reqDto
+ ) throws NotFoundException {
+ return stationService.create(reqDto);
+ }
+
+ @GetMapping(path = "/{uuid}", produces = MediaType.APPLICATION_JSON_VALUE)
+ public StationDTO getSingle(@PathVariable UUID uuid) throws NotFoundException {
+ return stationService.getSingle(uuid);
+ }
+
+ @GetMapping(path = "/{uuid}/trains", produces = MediaType.APPLICATION_JSON_VALUE)
+ public List getSuitableTrains(@PathVariable UUID uuid)
+ throws NotFoundException {
+ return stationService.getSuitableStations(uuid);
+ }
+
+ @PutMapping(
+ path = "/{uuid}",
+ consumes = MediaType.APPLICATION_JSON_VALUE,
+ produces = MediaType.APPLICATION_JSON_VALUE
+ )
+ public StationDTO update(
+ @PathVariable UUID uuid,
+ @RequestBody @Valid StationUpdateDTO reqDto
+ ) throws NotFoundException {
+ return stationService.update(uuid, reqDto);
+ }
+
+ @DeleteMapping(path = "/{uuid}", produces = MediaType.APPLICATION_JSON_VALUE)
+ public StationDTO delete(@PathVariable UUID uuid) throws NotFoundException {
+ return stationService.softDelete(uuid);
+ }
+}
diff --git a/src/main/java/org/fairdatatrain/trainhandler/api/controller/StationDirectoryController.java b/src/main/java/org/fairdatatrain/trainhandler/api/controller/StationDirectoryController.java
new file mode 100644
index 0000000..2b2c41e
--- /dev/null
+++ b/src/main/java/org/fairdatatrain/trainhandler/api/controller/StationDirectoryController.java
@@ -0,0 +1,96 @@
+/**
+ * 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.trainhandler.api.controller;
+
+import io.swagger.v3.oas.annotations.tags.Tag;
+import lombok.RequiredArgsConstructor;
+import org.fairdatatrain.trainhandler.api.dto.stationdirectory.StationDirectoryChangeDTO;
+import org.fairdatatrain.trainhandler.api.dto.stationdirectory.StationDirectoryDTO;
+import org.fairdatatrain.trainhandler.exception.NotFoundException;
+import org.fairdatatrain.trainhandler.service.stationdirectory.StationDirectoryService;
+import org.springframework.data.domain.Page;
+import org.springframework.data.domain.Pageable;
+import org.springframework.data.web.PageableDefault;
+import org.springframework.http.HttpStatus;
+import org.springframework.http.MediaType;
+import org.springframework.security.access.prepost.PreAuthorize;
+import org.springframework.web.bind.annotation.*;
+
+import javax.validation.Valid;
+import java.util.UUID;
+
+@Tag(name = "Station Directories")
+@PreAuthorize("hasRole('user')")
+@RestController
+@RequestMapping("/station-directories")
+@RequiredArgsConstructor
+public class StationDirectoryController {
+
+ private final StationDirectoryService stationDirectoryService;
+
+ @GetMapping(path = "", produces = MediaType.APPLICATION_JSON_VALUE)
+ public Page getPaged(
+ @RequestParam(value = "query", required = false, defaultValue = "") String query,
+ @PageableDefault(sort = "displayName") Pageable pageable) {
+ return stationDirectoryService.getPaged(query, pageable);
+ }
+
+ @PostMapping(
+ path = "",
+ consumes = MediaType.APPLICATION_JSON_VALUE,
+ produces = MediaType.APPLICATION_JSON_VALUE)
+ @ResponseStatus(code = HttpStatus.CREATED)
+ public StationDirectoryDTO create(
+ @Valid @RequestBody StationDirectoryChangeDTO reqDto
+ ) throws NotFoundException {
+ final StationDirectoryDTO dto = stationDirectoryService.create(reqDto);
+ stationDirectoryService.reindex(dto.getUuid());
+ return dto;
+ }
+
+ @GetMapping(path = "/{uuid}", produces = MediaType.APPLICATION_JSON_VALUE)
+ public StationDirectoryDTO getSingle(@PathVariable UUID uuid) throws NotFoundException {
+ return stationDirectoryService.getSingle(uuid);
+ }
+
+ @PutMapping(path = "/{uuid}", produces = MediaType.APPLICATION_JSON_VALUE)
+ public StationDirectoryDTO update(
+ @PathVariable UUID uuid, @Valid @RequestBody StationDirectoryChangeDTO reqDto)
+ throws NotFoundException {
+ final StationDirectoryDTO dto = stationDirectoryService.update(uuid, reqDto);
+ stationDirectoryService.reindex(dto.getUuid());
+ return dto;
+ }
+
+ @DeleteMapping(path = "/{uuid}")
+ @ResponseStatus(code = HttpStatus.NO_CONTENT)
+ public void delete(@PathVariable UUID uuid) throws NotFoundException {
+ stationDirectoryService.delete(uuid);
+ }
+
+ @PutMapping(path = "/{uuid}/stations", params = "refresh")
+ @ResponseStatus(code = HttpStatus.ACCEPTED)
+ public void refreshStations(@PathVariable UUID uuid) throws NotFoundException {
+ stationDirectoryService.reindex(uuid);
+ }
+}
diff --git a/src/main/java/org/fairdatatrain/trainhandler/api/controller/TrainController.java b/src/main/java/org/fairdatatrain/trainhandler/api/controller/TrainController.java
new file mode 100644
index 0000000..9ebd2ea
--- /dev/null
+++ b/src/main/java/org/fairdatatrain/trainhandler/api/controller/TrainController.java
@@ -0,0 +1,105 @@
+/**
+ * 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.trainhandler.api.controller;
+
+import io.swagger.v3.oas.annotations.tags.Tag;
+import lombok.RequiredArgsConstructor;
+import org.fairdatatrain.trainhandler.api.dto.station.StationSimpleDTO;
+import org.fairdatatrain.trainhandler.api.dto.train.TrainCreateDTO;
+import org.fairdatatrain.trainhandler.api.dto.train.TrainDTO;
+import org.fairdatatrain.trainhandler.api.dto.train.TrainSimpleDTO;
+import org.fairdatatrain.trainhandler.api.dto.train.TrainUpdateDTO;
+import org.fairdatatrain.trainhandler.exception.NotFoundException;
+import org.fairdatatrain.trainhandler.service.train.TrainService;
+import org.springframework.data.domain.Page;
+import org.springframework.data.domain.Pageable;
+import org.springframework.data.web.PageableDefault;
+import org.springframework.http.MediaType;
+import org.springframework.security.access.prepost.PreAuthorize;
+import org.springframework.web.bind.annotation.*;
+
+import javax.validation.Valid;
+import java.util.List;
+import java.util.UUID;
+
+@Tag(name = "Trains")
+@PreAuthorize("hasRole('user')")
+@RestController
+@RequestMapping("/trains")
+@RequiredArgsConstructor
+public class TrainController {
+
+ private final TrainService trainService;
+
+ @GetMapping(path = "", produces = MediaType.APPLICATION_JSON_VALUE)
+ public Page getPaged(
+ @RequestParam(value = "query", required = false, defaultValue = "") String query,
+ @PageableDefault(sort = "title") Pageable pageable) {
+ return trainService.getPaged(query, pageable);
+ }
+
+ @GetMapping(path = "/all", produces = MediaType.APPLICATION_JSON_VALUE)
+ public List getPaged(
+ @RequestParam(value = "query", required = false, defaultValue = "") String query) {
+ return trainService.getAll(query);
+ }
+
+ @PostMapping(
+ path = "",
+ consumes = MediaType.APPLICATION_JSON_VALUE,
+ produces = MediaType.APPLICATION_JSON_VALUE
+ )
+ public TrainDTO create(
+ @RequestBody @Valid TrainCreateDTO reqDto
+ ) throws NotFoundException {
+ return trainService.create(reqDto);
+ }
+
+ @GetMapping(path = "/{uuid}", produces = MediaType.APPLICATION_JSON_VALUE)
+ public TrainDTO getSingle(@PathVariable UUID uuid) throws NotFoundException {
+ return trainService.getSingle(uuid);
+ }
+
+ @GetMapping(path = "/{uuid}/stations", produces = MediaType.APPLICATION_JSON_VALUE)
+ public List getSuitableStations(@PathVariable UUID uuid)
+ throws NotFoundException {
+ return trainService.getSuitableStations(uuid);
+ }
+
+ @PutMapping(
+ path = "/{uuid}",
+ consumes = MediaType.APPLICATION_JSON_VALUE,
+ produces = MediaType.APPLICATION_JSON_VALUE
+ )
+ public TrainDTO update(
+ @PathVariable UUID uuid,
+ @RequestBody @Valid TrainUpdateDTO reqDto
+ ) throws NotFoundException {
+ return trainService.update(uuid, reqDto);
+ }
+
+ @DeleteMapping(path = "/{uuid}", produces = MediaType.APPLICATION_JSON_VALUE)
+ public TrainDTO delete(@PathVariable UUID uuid) throws NotFoundException {
+ return trainService.softDelete(uuid);
+ }
+}
diff --git a/src/main/java/org/fairdatatrain/trainhandler/api/controller/TrainGarageController.java b/src/main/java/org/fairdatatrain/trainhandler/api/controller/TrainGarageController.java
new file mode 100644
index 0000000..9643a0d
--- /dev/null
+++ b/src/main/java/org/fairdatatrain/trainhandler/api/controller/TrainGarageController.java
@@ -0,0 +1,96 @@
+/**
+ * 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.trainhandler.api.controller;
+
+import io.swagger.v3.oas.annotations.tags.Tag;
+import lombok.RequiredArgsConstructor;
+import org.fairdatatrain.trainhandler.api.dto.traingarage.TrainGarageChangeDTO;
+import org.fairdatatrain.trainhandler.api.dto.traingarage.TrainGarageDTO;
+import org.fairdatatrain.trainhandler.exception.NotFoundException;
+import org.fairdatatrain.trainhandler.service.traingarage.TrainGarageService;
+import org.springframework.data.domain.Page;
+import org.springframework.data.domain.Pageable;
+import org.springframework.data.web.PageableDefault;
+import org.springframework.http.HttpStatus;
+import org.springframework.http.MediaType;
+import org.springframework.security.access.prepost.PreAuthorize;
+import org.springframework.web.bind.annotation.*;
+
+import javax.validation.Valid;
+import java.util.UUID;
+
+@Tag(name = "Train Garages")
+@PreAuthorize("hasRole('user')")
+@RestController
+@RequestMapping("/train-garages")
+@RequiredArgsConstructor
+public class TrainGarageController {
+
+ private final TrainGarageService trainGarageService;
+
+ @GetMapping(path = "", produces = MediaType.APPLICATION_JSON_VALUE)
+ public Page getPaged(
+ @RequestParam(value = "query", required = false, defaultValue = "") String query,
+ @PageableDefault(sort = "displayName") Pageable pageable) {
+ return trainGarageService.getPaged(query, pageable);
+ }
+
+ @PostMapping(
+ path = "",
+ consumes = MediaType.APPLICATION_JSON_VALUE,
+ produces = MediaType.APPLICATION_JSON_VALUE)
+ @ResponseStatus(code = HttpStatus.CREATED)
+ public TrainGarageDTO create(
+ @Valid @RequestBody TrainGarageChangeDTO reqDto
+ ) throws NotFoundException {
+ final TrainGarageDTO dto = trainGarageService.create(reqDto);
+ trainGarageService.reindex(dto.getUuid());
+ return dto;
+ }
+
+ @GetMapping(path = "/{uuid}", produces = MediaType.APPLICATION_JSON_VALUE)
+ public TrainGarageDTO getSingle(@PathVariable UUID uuid) throws NotFoundException {
+ return trainGarageService.getSingle(uuid);
+ }
+
+ @PutMapping(path = "/{uuid}", produces = MediaType.APPLICATION_JSON_VALUE)
+ public TrainGarageDTO update(
+ @PathVariable UUID uuid, @Valid @RequestBody TrainGarageChangeDTO reqDto
+ ) throws NotFoundException {
+ final TrainGarageDTO dto = trainGarageService.update(uuid, reqDto);
+ trainGarageService.reindex(dto.getUuid());
+ return dto;
+ }
+
+ @DeleteMapping(path = "/{uuid}")
+ @ResponseStatus(code = HttpStatus.NO_CONTENT)
+ public void delete(@PathVariable UUID uuid) throws NotFoundException {
+ trainGarageService.delete(uuid);
+ }
+
+ @PutMapping(path = "/{uuid}/trains", params = "refresh")
+ @ResponseStatus(code = HttpStatus.ACCEPTED)
+ public void refreshTrains(@PathVariable UUID uuid) throws NotFoundException {
+ trainGarageService.reindex(uuid);
+ }
+}
diff --git a/src/main/java/org/fairdatatrain/trainhandler/api/controller/TrainTypeController.java b/src/main/java/org/fairdatatrain/trainhandler/api/controller/TrainTypeController.java
new file mode 100644
index 0000000..11049cf
--- /dev/null
+++ b/src/main/java/org/fairdatatrain/trainhandler/api/controller/TrainTypeController.java
@@ -0,0 +1,91 @@
+/**
+ * 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.trainhandler.api.controller;
+
+import io.swagger.v3.oas.annotations.tags.Tag;
+import lombok.RequiredArgsConstructor;
+import org.fairdatatrain.trainhandler.api.dto.traintype.TrainTypeChangeDTO;
+import org.fairdatatrain.trainhandler.api.dto.traintype.TrainTypeDTO;
+import org.fairdatatrain.trainhandler.api.dto.traintype.TrainTypeSimpleDTO;
+import org.fairdatatrain.trainhandler.exception.NotFoundException;
+import org.fairdatatrain.trainhandler.service.traintype.TrainTypeService;
+import org.springframework.data.domain.Page;
+import org.springframework.data.domain.Pageable;
+import org.springframework.data.web.PageableDefault;
+import org.springframework.http.HttpStatus;
+import org.springframework.http.MediaType;
+import org.springframework.security.access.prepost.PreAuthorize;
+import org.springframework.web.bind.annotation.*;
+
+import javax.validation.Valid;
+import java.util.List;
+import java.util.UUID;
+
+@Tag(name = "Train Types")
+@PreAuthorize("hasRole('user')")
+@RestController
+@RequestMapping("/train-types")
+@RequiredArgsConstructor
+public class TrainTypeController {
+
+ private final TrainTypeService trainTypeService;
+
+ @GetMapping(path = "", produces = MediaType.APPLICATION_JSON_VALUE)
+ public Page getPaged(
+ @RequestParam(value = "query", required = false, defaultValue = "") String query,
+ @PageableDefault(sort = "title") Pageable pageable) {
+ return trainTypeService.getPaged(query, pageable);
+ }
+
+ @GetMapping(path = "/all", produces = MediaType.APPLICATION_JSON_VALUE)
+ public List getAll() {
+ return trainTypeService.getAll();
+ }
+
+ @PostMapping(
+ path = "",
+ consumes = MediaType.APPLICATION_JSON_VALUE,
+ produces = MediaType.APPLICATION_JSON_VALUE)
+ @ResponseStatus(code = HttpStatus.CREATED)
+ public TrainTypeDTO create(@Valid @RequestBody TrainTypeChangeDTO reqDto) {
+ return trainTypeService.create(reqDto);
+ }
+
+ @GetMapping(path = "/{uuid}", produces = MediaType.APPLICATION_JSON_VALUE)
+ public TrainTypeDTO getSingle(@PathVariable UUID uuid) throws NotFoundException {
+ return trainTypeService.getSingle(uuid);
+ }
+
+ @PutMapping(path = "/{uuid}", produces = MediaType.APPLICATION_JSON_VALUE)
+ public TrainTypeDTO update(
+ @PathVariable UUID uuid, @Valid @RequestBody TrainTypeChangeDTO reqDto)
+ throws NotFoundException {
+ return trainTypeService.update(uuid, reqDto);
+ }
+
+ @DeleteMapping(path = "/{uuid}")
+ @ResponseStatus(code = HttpStatus.NO_CONTENT)
+ public void delete(@PathVariable UUID uuid) throws NotFoundException {
+ trainTypeService.delete(uuid);
+ }
+}
diff --git a/src/main/java/org/fairdatatrain/trainhandler/api/dto/error/ErrorDTO.java b/src/main/java/org/fairdatatrain/trainhandler/api/dto/error/ErrorDTO.java
new file mode 100644
index 0000000..2135093
--- /dev/null
+++ b/src/main/java/org/fairdatatrain/trainhandler/api/dto/error/ErrorDTO.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.trainhandler.api.dto.error;
+
+import lombok.AllArgsConstructor;
+import lombok.Getter;
+import lombok.NoArgsConstructor;
+import lombok.Setter;
+
+import java.util.Collections;
+import java.util.Map;
+
+@NoArgsConstructor
+@AllArgsConstructor
+@Getter
+@Setter
+public class ErrorDTO {
+
+ private String code;
+
+ private String message;
+
+ private Map fields = Collections.emptyMap();
+
+ public ErrorDTO(String code, String message) {
+ this.code = code;
+ this.message = message;
+ }
+}
diff --git a/src/main/java/org/fairdatatrain/trainhandler/api/dto/job/JobArtifactCreateDTO.java b/src/main/java/org/fairdatatrain/trainhandler/api/dto/job/JobArtifactCreateDTO.java
new file mode 100644
index 0000000..e2041cb
--- /dev/null
+++ b/src/main/java/org/fairdatatrain/trainhandler/api/dto/job/JobArtifactCreateDTO.java
@@ -0,0 +1,65 @@
+/**
+ * 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.trainhandler.api.dto.job;
+
+import lombok.AllArgsConstructor;
+import lombok.Getter;
+import lombok.NoArgsConstructor;
+import lombok.Setter;
+
+import javax.validation.constraints.NotNull;
+import java.time.Instant;
+
+@AllArgsConstructor
+@NoArgsConstructor
+@Getter
+@Setter
+public class 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/trainhandler/api/dto/job/JobArtifactDTO.java b/src/main/java/org/fairdatatrain/trainhandler/api/dto/job/JobArtifactDTO.java
new file mode 100644
index 0000000..4b9e7cd
--- /dev/null
+++ b/src/main/java/org/fairdatatrain/trainhandler/api/dto/job/JobArtifactDTO.java
@@ -0,0 +1,53 @@
+/**
+ * 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.trainhandler.api.dto.job;
+
+import lombok.*;
+
+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 String occurredAt;
+
+ private String createdAt;
+
+ private String updatedAt;
+}
diff --git a/src/main/java/org/fairdatatrain/trainhandler/api/dto/job/JobDTO.java b/src/main/java/org/fairdatatrain/trainhandler/api/dto/job/JobDTO.java
new file mode 100644
index 0000000..fb2577d
--- /dev/null
+++ b/src/main/java/org/fairdatatrain/trainhandler/api/dto/job/JobDTO.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.trainhandler.api.dto.job;
+
+import lombok.*;
+import org.fairdatatrain.trainhandler.api.dto.run.RunSimpleDTO;
+import org.fairdatatrain.trainhandler.api.dto.station.StationSimpleDTO;
+import org.fairdatatrain.trainhandler.data.model.enums.JobStatus;
+
+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 String startedAt;
+
+ private String finishedAt;
+
+ private StationSimpleDTO target;
+
+ private RunSimpleDTO run;
+
+ private List events;
+
+ private List artifacts;
+
+ private String createdAt;
+
+ private String updatedAt;
+
+ private Long version;
+}
diff --git a/src/main/java/org/fairdatatrain/trainhandler/api/dto/job/JobEventCreateDTO.java b/src/main/java/org/fairdatatrain/trainhandler/api/dto/job/JobEventCreateDTO.java
new file mode 100644
index 0000000..0b80f7e
--- /dev/null
+++ b/src/main/java/org/fairdatatrain/trainhandler/api/dto/job/JobEventCreateDTO.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.trainhandler.api.dto.job;
+
+import lombok.*;
+import org.fairdatatrain.trainhandler.data.model.enums.JobStatus;
+
+import javax.validation.constraints.NotNull;
+import java.time.Instant;
+
+@AllArgsConstructor
+@NoArgsConstructor
+@Getter
+@Setter
+@Builder
+public class JobEventCreateDTO {
+
+ 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/trainhandler/api/dto/job/JobEventDTO.java b/src/main/java/org/fairdatatrain/trainhandler/api/dto/job/JobEventDTO.java
new file mode 100644
index 0000000..451c13a
--- /dev/null
+++ b/src/main/java/org/fairdatatrain/trainhandler/api/dto/job/JobEventDTO.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.trainhandler.api.dto.job;
+
+import lombok.*;
+import org.fairdatatrain.trainhandler.data.model.enums.JobStatus;
+
+import java.util.UUID;
+
+@AllArgsConstructor
+@NoArgsConstructor
+@Getter
+@Setter
+@Builder(toBuilder = true)
+public class JobEventDTO {
+
+ private UUID uuid;
+
+ private JobStatus resultStatus;
+
+ private String message;
+
+ private String occurredAt;
+
+ private String createdAt;
+
+ private String updatedAt;
+}
diff --git a/src/main/java/org/fairdatatrain/trainhandler/api/dto/job/JobSimpleDTO.java b/src/main/java/org/fairdatatrain/trainhandler/api/dto/job/JobSimpleDTO.java
new file mode 100644
index 0000000..3893b79
--- /dev/null
+++ b/src/main/java/org/fairdatatrain/trainhandler/api/dto/job/JobSimpleDTO.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.trainhandler.api.dto.job;
+
+import lombok.*;
+import org.fairdatatrain.trainhandler.api.dto.station.StationSimpleDTO;
+import org.fairdatatrain.trainhandler.data.model.enums.JobStatus;
+
+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 String startedAt;
+
+ private String finishedAt;
+
+ private StationSimpleDTO target;
+
+ private List artifacts;
+
+ private UUID runUuid;
+
+ private Long version;
+}
diff --git a/src/main/java/org/fairdatatrain/trainhandler/api/dto/plan/PlanCreateDTO.java b/src/main/java/org/fairdatatrain/trainhandler/api/dto/plan/PlanCreateDTO.java
new file mode 100644
index 0000000..c659f4d
--- /dev/null
+++ b/src/main/java/org/fairdatatrain/trainhandler/api/dto/plan/PlanCreateDTO.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.trainhandler.api.dto.plan;
+
+import lombok.AllArgsConstructor;
+import lombok.Getter;
+import lombok.NoArgsConstructor;
+import lombok.Setter;
+
+import javax.validation.constraints.NotBlank;
+import javax.validation.constraints.NotNull;
+import java.util.List;
+import java.util.UUID;
+
+@AllArgsConstructor
+@NoArgsConstructor
+@Getter
+@Setter
+public class PlanCreateDTO {
+
+ @NotNull
+ private UUID trainUuid;
+
+ @NotNull
+ private List targets;
+
+ @NotBlank
+ private String displayName;
+
+ @NotNull
+ private String note;
+
+ @NotNull
+ private Boolean publishArtifacts = false;
+}
diff --git a/src/main/java/org/fairdatatrain/trainhandler/api/dto/plan/PlanDTO.java b/src/main/java/org/fairdatatrain/trainhandler/api/dto/plan/PlanDTO.java
new file mode 100644
index 0000000..3cc9c48
--- /dev/null
+++ b/src/main/java/org/fairdatatrain/trainhandler/api/dto/plan/PlanDTO.java
@@ -0,0 +1,53 @@
+/**
+ * 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.trainhandler.api.dto.plan;
+
+import lombok.*;
+import org.fairdatatrain.trainhandler.api.dto.train.TrainDTO;
+
+import java.util.List;
+import java.util.UUID;
+
+@AllArgsConstructor
+@NoArgsConstructor
+@Getter
+@Setter
+@Builder(toBuilder = true)
+public class PlanDTO {
+
+ private UUID uuid;
+
+ private String displayName;
+
+ private String note;
+
+ private TrainDTO train;
+
+ private List targets;
+
+ private Boolean publishArtifacts;
+
+ private String createdAt;
+
+ private String updatedAt;
+}
diff --git a/src/main/java/org/fairdatatrain/trainhandler/api/dto/plan/PlanSimpleDTO.java b/src/main/java/org/fairdatatrain/trainhandler/api/dto/plan/PlanSimpleDTO.java
new file mode 100644
index 0000000..9c05b5e
--- /dev/null
+++ b/src/main/java/org/fairdatatrain/trainhandler/api/dto/plan/PlanSimpleDTO.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.trainhandler.api.dto.plan;
+
+import lombok.*;
+import org.fairdatatrain.trainhandler.api.dto.train.TrainSimpleDTO;
+
+import javax.validation.constraints.NotNull;
+import java.util.List;
+import java.util.UUID;
+
+@AllArgsConstructor
+@NoArgsConstructor
+@Getter
+@Setter
+@Builder(toBuilder = true)
+public class PlanSimpleDTO {
+
+ private UUID uuid;
+
+ private String displayName;
+
+ private String note;
+
+ @NotNull
+ private TrainSimpleDTO train;
+
+ @NotNull
+ private List targets;
+
+ private Boolean publishArtifacts;
+}
diff --git a/src/main/java/org/fairdatatrain/trainhandler/api/dto/plan/PlanTargetChangeDTO.java b/src/main/java/org/fairdatatrain/trainhandler/api/dto/plan/PlanTargetChangeDTO.java
new file mode 100644
index 0000000..eb03d57
--- /dev/null
+++ b/src/main/java/org/fairdatatrain/trainhandler/api/dto/plan/PlanTargetChangeDTO.java
@@ -0,0 +1,42 @@
+/**
+ * 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.trainhandler.api.dto.plan;
+
+import lombok.*;
+
+import javax.validation.constraints.NotNull;
+import java.util.UUID;
+
+@AllArgsConstructor
+@NoArgsConstructor
+@Getter
+@Setter
+@Builder
+public class PlanTargetChangeDTO {
+
+ @NotNull
+ private UUID stationUuid;
+
+ @NotNull
+ private Boolean publishArtifacts = false;
+}
diff --git a/src/main/java/org/fairdatatrain/trainhandler/api/dto/plan/PlanTargetDTO.java b/src/main/java/org/fairdatatrain/trainhandler/api/dto/plan/PlanTargetDTO.java
new file mode 100644
index 0000000..80b8c9b
--- /dev/null
+++ b/src/main/java/org/fairdatatrain/trainhandler/api/dto/plan/PlanTargetDTO.java
@@ -0,0 +1,42 @@
+/**
+ * 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.trainhandler.api.dto.plan;
+
+import lombok.*;
+import org.fairdatatrain.trainhandler.api.dto.station.StationSimpleDTO;
+
+import javax.validation.constraints.NotNull;
+
+@AllArgsConstructor
+@NoArgsConstructor
+@Getter
+@Setter
+@Builder
+public class PlanTargetDTO {
+
+ @NotNull
+ private StationSimpleDTO station;
+
+ @NotNull
+ private Boolean publishArtifacts = false;
+}
diff --git a/src/main/java/org/fairdatatrain/trainhandler/api/dto/plan/PlanUpdateDTO.java b/src/main/java/org/fairdatatrain/trainhandler/api/dto/plan/PlanUpdateDTO.java
new file mode 100644
index 0000000..f097aae
--- /dev/null
+++ b/src/main/java/org/fairdatatrain/trainhandler/api/dto/plan/PlanUpdateDTO.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.trainhandler.api.dto.plan;
+
+import lombok.AllArgsConstructor;
+import lombok.Getter;
+import lombok.NoArgsConstructor;
+import lombok.Setter;
+
+import javax.validation.constraints.NotBlank;
+import javax.validation.constraints.NotNull;
+import java.util.List;
+import java.util.UUID;
+
+@AllArgsConstructor
+@NoArgsConstructor
+@Getter
+@Setter
+public class PlanUpdateDTO {
+
+ @NotNull
+ private UUID trainUuid;
+
+ @NotNull
+ private List targets;
+
+ @NotBlank
+ private String displayName;
+
+ @NotNull
+ private String note;
+
+ private Boolean publishArtifacts = false;
+}
diff --git a/src/main/java/org/fairdatatrain/trainhandler/api/dto/run/RunCreateDTO.java b/src/main/java/org/fairdatatrain/trainhandler/api/dto/run/RunCreateDTO.java
new file mode 100644
index 0000000..7ec88b6
--- /dev/null
+++ b/src/main/java/org/fairdatatrain/trainhandler/api/dto/run/RunCreateDTO.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.trainhandler.api.dto.run;
+
+import lombok.AllArgsConstructor;
+import lombok.Getter;
+import lombok.NoArgsConstructor;
+import lombok.Setter;
+
+import javax.validation.constraints.NotBlank;
+import javax.validation.constraints.NotNull;
+import java.time.Instant;
+import java.util.UUID;
+
+@AllArgsConstructor
+@NoArgsConstructor
+@Getter
+@Setter
+public class RunCreateDTO {
+
+ @NotBlank
+ private String displayName;
+
+ @NotNull
+ private String note;
+
+ @NotNull
+ private UUID planUuid;
+
+ private Instant shouldStartAt;
+}
diff --git a/src/main/java/org/fairdatatrain/trainhandler/api/dto/run/RunDTO.java b/src/main/java/org/fairdatatrain/trainhandler/api/dto/run/RunDTO.java
new file mode 100644
index 0000000..3c2b24c
--- /dev/null
+++ b/src/main/java/org/fairdatatrain/trainhandler/api/dto/run/RunDTO.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.trainhandler.api.dto.run;
+
+import lombok.*;
+import org.fairdatatrain.trainhandler.api.dto.job.JobSimpleDTO;
+import org.fairdatatrain.trainhandler.api.dto.plan.PlanSimpleDTO;
+import org.fairdatatrain.trainhandler.data.model.enums.RunStatus;
+
+import java.util.List;
+import java.util.UUID;
+
+@AllArgsConstructor
+@NoArgsConstructor
+@Getter
+@Setter
+@Builder(toBuilder = true)
+public class RunDTO {
+
+ private UUID uuid;
+
+ private String displayName;
+
+ private String note;
+
+ private RunStatus status;
+
+ private PlanSimpleDTO plan;
+
+ private List jobs;
+
+ private String shouldStartAt;
+
+ private String startedAt;
+
+ private String finishedAt;
+
+ private String createdAt;
+
+ private String updatedAt;
+
+ private Long version;
+}
diff --git a/src/main/java/org/fairdatatrain/trainhandler/api/dto/run/RunSimpleDTO.java b/src/main/java/org/fairdatatrain/trainhandler/api/dto/run/RunSimpleDTO.java
new file mode 100644
index 0000000..ab47fd4
--- /dev/null
+++ b/src/main/java/org/fairdatatrain/trainhandler/api/dto/run/RunSimpleDTO.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.trainhandler.api.dto.run;
+
+import lombok.*;
+import org.fairdatatrain.trainhandler.data.model.enums.RunStatus;
+
+import java.util.UUID;
+
+@AllArgsConstructor
+@NoArgsConstructor
+@Getter
+@Setter
+@Builder(toBuilder = true)
+public class RunSimpleDTO {
+
+ private UUID uuid;
+
+ private String displayName;
+
+ private String note;
+
+ private RunStatus status;
+
+ private UUID trainUuid;
+
+ private UUID planUuid;
+
+ private String shouldStartAt;
+
+ private String startedAt;
+
+ private String finishedAt;
+
+ private String createdAt;
+
+ private String updatedAt;
+}
diff --git a/src/main/java/org/fairdatatrain/trainhandler/api/dto/run/RunUpdateDTO.java b/src/main/java/org/fairdatatrain/trainhandler/api/dto/run/RunUpdateDTO.java
new file mode 100644
index 0000000..2018ebe
--- /dev/null
+++ b/src/main/java/org/fairdatatrain/trainhandler/api/dto/run/RunUpdateDTO.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.trainhandler.api.dto.run;
+
+import lombok.AllArgsConstructor;
+import lombok.Getter;
+import lombok.NoArgsConstructor;
+import lombok.Setter;
+
+import javax.validation.constraints.NotBlank;
+import javax.validation.constraints.NotNull;
+
+@AllArgsConstructor
+@NoArgsConstructor
+@Getter
+@Setter
+public class RunUpdateDTO {
+
+ @NotBlank
+ private String displayName;
+
+ @NotNull
+ private String note;
+}
diff --git a/src/main/java/org/fairdatatrain/trainhandler/api/dto/station/StationCreateDTO.java b/src/main/java/org/fairdatatrain/trainhandler/api/dto/station/StationCreateDTO.java
new file mode 100644
index 0000000..ec206ce
--- /dev/null
+++ b/src/main/java/org/fairdatatrain/trainhandler/api/dto/station/StationCreateDTO.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.trainhandler.api.dto.station;
+
+import lombok.*;
+
+import javax.validation.constraints.NotNull;
+import java.util.List;
+import java.util.UUID;
+
+@AllArgsConstructor
+@NoArgsConstructor
+@Getter
+@Setter
+@Builder(toBuilder = true)
+public class StationCreateDTO {
+
+ @NotNull
+ private String uri;
+
+ @NotNull
+ private String title;
+
+ @NotNull
+ private String description;
+
+ @NotNull
+ private String endpointUrl;
+
+ @NotNull
+ private String endpointDescription;
+
+ @NotNull
+ private List keywords;
+
+ @NotNull
+ private String metadata;
+
+ @NotNull
+ private List trainTypeUuids;
+
+ @NotNull
+ private Boolean fetch = false;
+}
diff --git a/src/main/java/org/fairdatatrain/trainhandler/api/dto/station/StationDTO.java b/src/main/java/org/fairdatatrain/trainhandler/api/dto/station/StationDTO.java
new file mode 100644
index 0000000..d15ad9a
--- /dev/null
+++ b/src/main/java/org/fairdatatrain/trainhandler/api/dto/station/StationDTO.java
@@ -0,0 +1,69 @@
+/**
+ * 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.trainhandler.api.dto.station;
+
+import lombok.*;
+import org.fairdatatrain.trainhandler.api.dto.stationdirectory.StationDirectorySimpleDTO;
+import org.fairdatatrain.trainhandler.api.dto.traintype.TrainTypeSimpleDTO;
+import org.fairdatatrain.trainhandler.data.model.enums.SyncItemStatus;
+
+import java.util.List;
+import java.util.UUID;
+
+@AllArgsConstructor
+@NoArgsConstructor
+@Getter
+@Setter
+@Builder(toBuilder = true)
+public class StationDTO {
+
+ private UUID uuid;
+
+ private String uri;
+
+ private String title;
+
+ private String description;
+
+ private List keywords;
+
+ private String endpointUrl;
+
+ private String endpointDescription;
+
+ private SyncItemStatus status;
+
+ private String metadata;
+
+ private Boolean softDeleted;
+
+ private StationDirectorySimpleDTO directory;
+
+ private List types;
+
+ private String lastContactAt;
+
+ private String createdAt;
+
+ private String updatedAt;
+}
diff --git a/src/main/java/org/fairdatatrain/trainhandler/api/dto/station/StationSimpleDTO.java b/src/main/java/org/fairdatatrain/trainhandler/api/dto/station/StationSimpleDTO.java
new file mode 100644
index 0000000..168f7e5
--- /dev/null
+++ b/src/main/java/org/fairdatatrain/trainhandler/api/dto/station/StationSimpleDTO.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.trainhandler.api.dto.station;
+
+import lombok.*;
+import org.fairdatatrain.trainhandler.api.dto.stationdirectory.StationDirectorySimpleDTO;
+import org.fairdatatrain.trainhandler.api.dto.traintype.TrainTypeSimpleDTO;
+import org.fairdatatrain.trainhandler.data.model.enums.SyncItemStatus;
+
+import java.util.List;
+import java.util.UUID;
+
+@AllArgsConstructor
+@NoArgsConstructor
+@Getter
+@Setter
+@Builder(toBuilder = true)
+public class StationSimpleDTO {
+
+ private UUID uuid;
+
+ private String uri;
+
+ private String title;
+
+ private List keywords;
+
+ private String endpointUrl;
+
+ private String endpointDescription;
+
+ private SyncItemStatus status;
+
+ private StationDirectorySimpleDTO directory;
+
+ private List types;
+}
diff --git a/src/main/java/org/fairdatatrain/trainhandler/api/dto/station/StationUpdateDTO.java b/src/main/java/org/fairdatatrain/trainhandler/api/dto/station/StationUpdateDTO.java
new file mode 100644
index 0000000..8adec9a
--- /dev/null
+++ b/src/main/java/org/fairdatatrain/trainhandler/api/dto/station/StationUpdateDTO.java
@@ -0,0 +1,67 @@
+/**
+ * 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.trainhandler.api.dto.station;
+
+import lombok.*;
+
+import javax.validation.constraints.NotNull;
+import java.util.List;
+import java.util.UUID;
+
+@AllArgsConstructor
+@NoArgsConstructor
+@Getter
+@Setter
+@Builder(toBuilder = true)
+public class StationUpdateDTO {
+
+ @NotNull
+ private String uri;
+
+ @NotNull
+ private String title;
+
+ @NotNull
+ private String description;
+
+ @NotNull
+ private List keywords;
+
+ @NotNull
+ private String endpointUrl;
+
+ @NotNull
+ private String endpointDescription;
+
+ @NotNull
+ private String metadata;
+
+ @NotNull
+ private List trainTypeUuids;
+
+ private Boolean softDeleted;
+
+ @NotNull
+ private Boolean fetch = false;
+
+}
diff --git a/src/main/java/org/fairdatatrain/trainhandler/api/dto/stationdirectory/StationDirectoryChangeDTO.java b/src/main/java/org/fairdatatrain/trainhandler/api/dto/stationdirectory/StationDirectoryChangeDTO.java
new file mode 100644
index 0000000..6dff10d
--- /dev/null
+++ b/src/main/java/org/fairdatatrain/trainhandler/api/dto/stationdirectory/StationDirectoryChangeDTO.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.trainhandler.api.dto.stationdirectory;
+
+import lombok.*;
+
+import javax.validation.constraints.NotBlank;
+import javax.validation.constraints.NotNull;
+
+@NoArgsConstructor
+@AllArgsConstructor
+@Getter
+@Setter
+@Builder(toBuilder = true)
+public class StationDirectoryChangeDTO {
+
+ @NotBlank
+ private String uri;
+
+ @NotBlank
+ private String displayName;
+
+ @NotNull
+ private String note;
+
+}
diff --git a/src/main/java/org/fairdatatrain/trainhandler/api/dto/stationdirectory/StationDirectoryDTO.java b/src/main/java/org/fairdatatrain/trainhandler/api/dto/stationdirectory/StationDirectoryDTO.java
new file mode 100644
index 0000000..3d3d635
--- /dev/null
+++ b/src/main/java/org/fairdatatrain/trainhandler/api/dto/stationdirectory/StationDirectoryDTO.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.trainhandler.api.dto.stationdirectory;
+
+import lombok.*;
+import org.fairdatatrain.trainhandler.data.model.enums.SyncServiceStatus;
+
+import java.util.UUID;
+
+@NoArgsConstructor
+@AllArgsConstructor
+@Getter
+@Setter
+@Builder(toBuilder = true)
+public class StationDirectoryDTO {
+
+ private UUID uuid;
+
+ private String uri;
+
+ private String displayName;
+
+ private String note;
+
+ private String metadata;
+
+ private SyncServiceStatus status;
+
+ private Boolean deletable;
+
+ private String lastContactAt;
+
+ private String createdAt;
+
+ private String updatedAt;
+}
diff --git a/src/main/java/org/fairdatatrain/trainhandler/api/dto/stationdirectory/StationDirectorySimpleDTO.java b/src/main/java/org/fairdatatrain/trainhandler/api/dto/stationdirectory/StationDirectorySimpleDTO.java
new file mode 100644
index 0000000..8ee852d
--- /dev/null
+++ b/src/main/java/org/fairdatatrain/trainhandler/api/dto/stationdirectory/StationDirectorySimpleDTO.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.trainhandler.api.dto.stationdirectory;
+
+import lombok.*;
+import org.fairdatatrain.trainhandler.data.model.enums.SyncServiceStatus;
+
+import java.util.UUID;
+
+@NoArgsConstructor
+@AllArgsConstructor
+@Getter
+@Setter
+@Builder(toBuilder = true)
+public class StationDirectorySimpleDTO {
+
+ private UUID uuid;
+
+ private String uri;
+
+ private String displayName;
+
+ private SyncServiceStatus status;
+}
diff --git a/src/main/java/org/fairdatatrain/trainhandler/api/dto/train/TrainCreateDTO.java b/src/main/java/org/fairdatatrain/trainhandler/api/dto/train/TrainCreateDTO.java
new file mode 100644
index 0000000..c27bd3f
--- /dev/null
+++ b/src/main/java/org/fairdatatrain/trainhandler/api/dto/train/TrainCreateDTO.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.trainhandler.api.dto.train;
+
+import lombok.*;
+
+import javax.validation.constraints.NotNull;
+import java.util.List;
+import java.util.UUID;
+
+@AllArgsConstructor
+@NoArgsConstructor
+@Getter
+@Setter
+@Builder(toBuilder = true)
+public class TrainCreateDTO {
+
+ @NotNull
+ private String uri;
+
+ @NotNull
+ private String title;
+
+ @NotNull
+ private String description;
+
+ @NotNull
+ private List keywords;
+
+ @NotNull
+ private String metadata;
+
+ @NotNull
+ private List trainTypeUuids;
+
+ @NotNull
+ private Boolean fetch = false;
+}
diff --git a/src/main/java/org/fairdatatrain/trainhandler/api/dto/train/TrainDTO.java b/src/main/java/org/fairdatatrain/trainhandler/api/dto/train/TrainDTO.java
new file mode 100644
index 0000000..828de4d
--- /dev/null
+++ b/src/main/java/org/fairdatatrain/trainhandler/api/dto/train/TrainDTO.java
@@ -0,0 +1,65 @@
+/**
+ * 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.trainhandler.api.dto.train;
+
+import lombok.*;
+import org.fairdatatrain.trainhandler.api.dto.traingarage.TrainGarageSimpleDTO;
+import org.fairdatatrain.trainhandler.api.dto.traintype.TrainTypeSimpleDTO;
+import org.fairdatatrain.trainhandler.data.model.enums.SyncItemStatus;
+
+import java.util.List;
+import java.util.UUID;
+
+@AllArgsConstructor
+@NoArgsConstructor
+@Getter
+@Setter
+@Builder(toBuilder = true)
+public class TrainDTO {
+
+ private UUID uuid;
+
+ private String uri;
+
+ private String title;
+
+ private String description;
+
+ private List keywords;
+
+ private SyncItemStatus status;
+
+ private String metadata;
+
+ private Boolean softDeleted;
+
+ private TrainGarageSimpleDTO garage;
+
+ private List types;
+
+ private String lastContactAt;
+
+ private String createdAt;
+
+ private String updatedAt;
+}
diff --git a/src/main/java/org/fairdatatrain/trainhandler/api/dto/train/TrainSimpleDTO.java b/src/main/java/org/fairdatatrain/trainhandler/api/dto/train/TrainSimpleDTO.java
new file mode 100644
index 0000000..32688d7
--- /dev/null
+++ b/src/main/java/org/fairdatatrain/trainhandler/api/dto/train/TrainSimpleDTO.java
@@ -0,0 +1,53 @@
+/**
+ * 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.trainhandler.api.dto.train;
+
+import lombok.*;
+import org.fairdatatrain.trainhandler.api.dto.traingarage.TrainGarageSimpleDTO;
+import org.fairdatatrain.trainhandler.api.dto.traintype.TrainTypeSimpleDTO;
+import org.fairdatatrain.trainhandler.data.model.enums.SyncItemStatus;
+
+import java.util.List;
+import java.util.UUID;
+
+@AllArgsConstructor
+@NoArgsConstructor
+@Getter
+@Setter
+@Builder(toBuilder = true)
+public class TrainSimpleDTO {
+
+ private UUID uuid;
+
+ private String uri;
+
+ private String title;
+
+ private List keywords;
+
+ private SyncItemStatus status;
+
+ private TrainGarageSimpleDTO garage;
+
+ private List types;
+}
diff --git a/src/main/java/org/fairdatatrain/trainhandler/api/dto/train/TrainUpdateDTO.java b/src/main/java/org/fairdatatrain/trainhandler/api/dto/train/TrainUpdateDTO.java
new file mode 100644
index 0000000..ebc4f83
--- /dev/null
+++ b/src/main/java/org/fairdatatrain/trainhandler/api/dto/train/TrainUpdateDTO.java
@@ -0,0 +1,61 @@
+/**
+ * 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.trainhandler.api.dto.train;
+
+import lombok.*;
+
+import javax.validation.constraints.NotNull;
+import java.util.List;
+import java.util.UUID;
+
+@AllArgsConstructor
+@NoArgsConstructor
+@Getter
+@Setter
+@Builder(toBuilder = true)
+public class TrainUpdateDTO {
+
+ @NotNull
+ private String uri;
+
+ @NotNull
+ private String title;
+
+ @NotNull
+ private String description;
+
+ @NotNull
+ private List keywords;
+
+ @NotNull
+ private String metadata;
+
+ @NotNull
+ private List trainTypeUuids;
+
+ private Boolean softDeleted;
+
+ @NotNull
+ private Boolean fetch = false;
+
+}
diff --git a/src/main/java/org/fairdatatrain/trainhandler/api/dto/traingarage/TrainGarageChangeDTO.java b/src/main/java/org/fairdatatrain/trainhandler/api/dto/traingarage/TrainGarageChangeDTO.java
new file mode 100644
index 0000000..1283055
--- /dev/null
+++ b/src/main/java/org/fairdatatrain/trainhandler/api/dto/traingarage/TrainGarageChangeDTO.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.trainhandler.api.dto.traingarage;
+
+import lombok.*;
+
+import javax.validation.constraints.NotBlank;
+import javax.validation.constraints.NotNull;
+
+@NoArgsConstructor
+@AllArgsConstructor
+@Getter
+@Setter
+@Builder(toBuilder = true)
+public class TrainGarageChangeDTO {
+
+ @NotBlank
+ private String uri;
+
+ @NotBlank
+ private String displayName;
+
+ @NotNull
+ private String note;
+
+}
diff --git a/src/main/java/org/fairdatatrain/trainhandler/api/dto/traingarage/TrainGarageDTO.java b/src/main/java/org/fairdatatrain/trainhandler/api/dto/traingarage/TrainGarageDTO.java
new file mode 100644
index 0000000..8f40aac
--- /dev/null
+++ b/src/main/java/org/fairdatatrain/trainhandler/api/dto/traingarage/TrainGarageDTO.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.trainhandler.api.dto.traingarage;
+
+import lombok.*;
+import org.fairdatatrain.trainhandler.data.model.enums.SyncServiceStatus;
+
+import java.util.UUID;
+
+@NoArgsConstructor
+@AllArgsConstructor
+@Getter
+@Setter
+@Builder(toBuilder = true)
+public class TrainGarageDTO {
+
+ private UUID uuid;
+
+ private String uri;
+
+ private String displayName;
+
+ private String note;
+
+ private String metadata;
+
+ private SyncServiceStatus status;
+
+ private Boolean deletable;
+
+ private String lastContactAt;
+
+ private String createdAt;
+
+ private String updatedAt;
+}
+
diff --git a/src/main/java/org/fairdatatrain/trainhandler/api/dto/traingarage/TrainGarageSimpleDTO.java b/src/main/java/org/fairdatatrain/trainhandler/api/dto/traingarage/TrainGarageSimpleDTO.java
new file mode 100644
index 0000000..7843b57
--- /dev/null
+++ b/src/main/java/org/fairdatatrain/trainhandler/api/dto/traingarage/TrainGarageSimpleDTO.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.trainhandler.api.dto.traingarage;
+
+import lombok.*;
+import org.fairdatatrain.trainhandler.data.model.enums.SyncServiceStatus;
+
+import java.util.UUID;
+
+@NoArgsConstructor
+@AllArgsConstructor
+@Getter
+@Setter
+@Builder(toBuilder = true)
+public class TrainGarageSimpleDTO {
+
+ private UUID uuid;
+
+ private String uri;
+
+ private String displayName;
+
+ private SyncServiceStatus status;
+}
diff --git a/src/main/java/org/fairdatatrain/trainhandler/api/dto/traintype/TrainTypeChangeDTO.java b/src/main/java/org/fairdatatrain/trainhandler/api/dto/traintype/TrainTypeChangeDTO.java
new file mode 100644
index 0000000..48a160f
--- /dev/null
+++ b/src/main/java/org/fairdatatrain/trainhandler/api/dto/traintype/TrainTypeChangeDTO.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.trainhandler.api.dto.traintype;
+
+import lombok.*;
+
+import javax.validation.constraints.NotBlank;
+import javax.validation.constraints.NotNull;
+
+@AllArgsConstructor
+@NoArgsConstructor
+@Getter
+@Setter
+@Builder(toBuilder = true)
+public class TrainTypeChangeDTO {
+ @NotBlank
+ private String uri;
+
+ @NotBlank
+ private String title;
+
+ @NotNull
+ private String note;
+}
diff --git a/src/main/java/org/fairdatatrain/trainhandler/api/dto/traintype/TrainTypeDTO.java b/src/main/java/org/fairdatatrain/trainhandler/api/dto/traintype/TrainTypeDTO.java
new file mode 100644
index 0000000..2040b62
--- /dev/null
+++ b/src/main/java/org/fairdatatrain/trainhandler/api/dto/traintype/TrainTypeDTO.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.trainhandler.api.dto.traintype;
+
+import lombok.*;
+
+import java.util.UUID;
+
+@AllArgsConstructor
+@NoArgsConstructor
+@Getter
+@Setter
+@Builder(toBuilder = true)
+public class TrainTypeDTO {
+
+ private UUID uuid;
+
+ private String uri;
+
+ private String title;
+
+ private String note;
+
+ private String createdAt;
+
+ private String updatedAt;
+}
diff --git a/src/main/java/org/fairdatatrain/trainhandler/api/dto/traintype/TrainTypeSimpleDTO.java b/src/main/java/org/fairdatatrain/trainhandler/api/dto/traintype/TrainTypeSimpleDTO.java
new file mode 100644
index 0000000..226586a
--- /dev/null
+++ b/src/main/java/org/fairdatatrain/trainhandler/api/dto/traintype/TrainTypeSimpleDTO.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.trainhandler.api.dto.traintype;
+
+import lombok.*;
+
+import java.util.UUID;
+
+@AllArgsConstructor
+@NoArgsConstructor
+@Getter
+@Setter
+@Builder(toBuilder = true)
+public class TrainTypeSimpleDTO {
+
+ private UUID uuid;
+
+ private String uri;
+
+ private String title;
+}
diff --git a/src/main/java/org/fairdatatrain/trainhandler/api/filter/CorsFilter.java b/src/main/java/org/fairdatatrain/trainhandler/api/filter/CorsFilter.java
new file mode 100644
index 0000000..b55ed77
--- /dev/null
+++ b/src/main/java/org/fairdatatrain/trainhandler/api/filter/CorsFilter.java
@@ -0,0 +1,80 @@
+/**
+ * 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.trainhandler.api.filter;
+
+import org.springframework.http.HttpHeaders;
+import org.springframework.lang.Nullable;
+import org.springframework.stereotype.Component;
+import org.springframework.web.bind.annotation.RequestMethod;
+import org.springframework.web.filter.OncePerRequestFilter;
+
+import javax.servlet.FilterChain;
+import javax.servlet.ServletException;
+import javax.servlet.http.HttpServletRequest;
+import javax.servlet.http.HttpServletResponse;
+import java.io.IOException;
+
+@Component
+public class CorsFilter extends OncePerRequestFilter {
+
+ 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 void doFilterInternal(
+ @Nullable final HttpServletRequest request,
+ final HttpServletResponse response,
+ final FilterChain filterChain
+ ) throws IOException, ServletException {
+ logger.debug("Setting CORS headers (via request filter)");
+ response.setHeader(HttpHeaders.ACCESS_CONTROL_ALLOW_ORIGIN, "*");
+ response.setHeader(HttpHeaders.ALLOW, ALLOWED_METHODS);
+ response.setHeader(HttpHeaders.ACCESS_CONTROL_ALLOW_METHODS, ALLOWED_METHODS);
+ response.setHeader(HttpHeaders.ACCESS_CONTROL_ALLOW_HEADERS, ALLOWED_HEADERS);
+ response.setHeader(HttpHeaders.ACCESS_CONTROL_EXPOSE_HEADERS, EXPOSED_HEADERS);
+
+ filterChain.doFilter(request, response);
+ }
+}
diff --git a/src/main/java/org/fairdatatrain/trainhandler/api/filter/LoggingFilter.java b/src/main/java/org/fairdatatrain/trainhandler/api/filter/LoggingFilter.java
new file mode 100644
index 0000000..987caf7
--- /dev/null
+++ b/src/main/java/org/fairdatatrain/trainhandler/api/filter/LoggingFilter.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.trainhandler.api.filter;
+
+import org.apache.logging.log4j.ThreadContext;
+import org.springframework.http.HttpHeaders;
+import org.springframework.stereotype.Component;
+import org.springframework.web.filter.OncePerRequestFilter;
+
+import javax.servlet.FilterChain;
+import javax.servlet.ServletException;
+import javax.servlet.http.HttpServletRequest;
+import javax.servlet.http.HttpServletResponse;
+import java.io.IOException;
+import java.util.UUID;
+
+import static java.lang.String.format;
+
+@Component
+public class LoggingFilter extends OncePerRequestFilter {
+
+ @Override
+ public void doFilterInternal(
+ final HttpServletRequest request,
+ final HttpServletResponse response,
+ final FilterChain fc
+ ) throws IOException, ServletException {
+ ThreadContext.put("ipAddress", request.getRemoteAddr());
+ ThreadContext.put("requestMethod", request.getMethod());
+ ThreadContext.put("requestURI", request.getRequestURI());
+ ThreadContext.put("requestProtocol", request.getProtocol());
+ ThreadContext.put("responseStatus", String.valueOf(response.getStatus()));
+ ThreadContext.put("contentSize", response.getHeader(HttpHeaders.CONTENT_LENGTH));
+ ThreadContext.put("traceId", UUID.randomUUID().toString());
+
+ logger.info(format("%s %s", request.getMethod(), request.getRequestURL()));
+ fc.doFilter(request, response);
+ logger.info(format(
+ "%s %s [Response: %s]",
+ request.getMethod(), request.getRequestURL(), response.getStatus())
+ );
+
+ ThreadContext.clearAll();
+ }
+
+}
diff --git a/src/main/java/org/fairdatatrain/trainhandler/config/DispatcherConfig.java b/src/main/java/org/fairdatatrain/trainhandler/config/DispatcherConfig.java
new file mode 100644
index 0000000..ad04a4e
--- /dev/null
+++ b/src/main/java/org/fairdatatrain/trainhandler/config/DispatcherConfig.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.trainhandler.config;
+
+import lombok.AllArgsConstructor;
+import lombok.Getter;
+import lombok.NoArgsConstructor;
+import lombok.Setter;
+import org.fairdatatrain.trainhandler.config.properties.DispatchProperties;
+import org.fairdatatrain.trainhandler.config.properties.PollingProperties;
+import org.springframework.boot.context.properties.ConfigurationProperties;
+import org.springframework.boot.context.properties.EnableConfigurationProperties;
+import org.springframework.context.annotation.Configuration;
+
+@Getter
+@Setter
+@NoArgsConstructor
+@AllArgsConstructor
+@Configuration
+@EnableConfigurationProperties
+@ConfigurationProperties(prefix = "dispatcher")
+public class DispatcherConfig {
+
+ private DispatchProperties dispatch;
+
+ private PollingProperties polling;
+}
diff --git a/src/main/java/org/fairdatatrain/trainhandler/config/HttpClientConfig.java b/src/main/java/org/fairdatatrain/trainhandler/config/HttpClientConfig.java
new file mode 100644
index 0000000..05c1e70
--- /dev/null
+++ b/src/main/java/org/fairdatatrain/trainhandler/config/HttpClientConfig.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.trainhandler.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 HttpClientConfig {
+ private static final long TIMEOUT = 5;
+
+ @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/trainhandler/config/PropertiesConfig.java b/src/main/java/org/fairdatatrain/trainhandler/config/PropertiesConfig.java
new file mode 100644
index 0000000..f177734
--- /dev/null
+++ b/src/main/java/org/fairdatatrain/trainhandler/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.trainhandler.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/trainhandler/config/SecurityDisabledConfig.java b/src/main/java/org/fairdatatrain/trainhandler/config/SecurityDisabledConfig.java
new file mode 100644
index 0000000..39a66fe
--- /dev/null
+++ b/src/main/java/org/fairdatatrain/trainhandler/config/SecurityDisabledConfig.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.trainhandler.config;
+
+import org.springframework.boot.autoconfigure.condition.ConditionalOnProperty;
+import org.springframework.context.annotation.Bean;
+import org.springframework.context.annotation.Configuration;
+import org.springframework.security.config.annotation.web.builders.HttpSecurity;
+import org.springframework.security.web.SecurityFilterChain;
+
+@Configuration
+@ConditionalOnProperty(name = "keycloak.enabled", havingValue = "false")
+public class SecurityDisabledConfig {
+
+ @Bean
+ public SecurityFilterChain filterChain(final HttpSecurity http) throws Exception {
+ return http
+ .csrf().disable()
+ .authorizeHttpRequests(auth -> {
+ auth
+ .anyRequest()
+ .permitAll();
+ })
+ .build();
+ }
+}
diff --git a/src/main/java/org/fairdatatrain/trainhandler/config/SecurityKeycloakConfig.java b/src/main/java/org/fairdatatrain/trainhandler/config/SecurityKeycloakConfig.java
new file mode 100644
index 0000000..36bbb8a
--- /dev/null
+++ b/src/main/java/org/fairdatatrain/trainhandler/config/SecurityKeycloakConfig.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.trainhandler.config;
+
+import org.keycloak.adapters.springboot.KeycloakSpringBootConfigResolver;
+import org.keycloak.adapters.springsecurity.KeycloakConfiguration;
+import org.keycloak.adapters.springsecurity.authentication.KeycloakAuthenticationProvider;
+import org.keycloak.adapters.springsecurity.config.KeycloakWebSecurityConfigurerAdapter;
+import org.springframework.beans.factory.annotation.Autowired;
+import org.springframework.boot.autoconfigure.condition.ConditionalOnProperty;
+import org.springframework.context.annotation.Bean;
+import org.springframework.context.annotation.Import;
+import org.springframework.security.config.annotation.authentication.builders.AuthenticationManagerBuilder;
+import org.springframework.security.config.annotation.method.configuration.EnableMethodSecurity;
+import org.springframework.security.config.annotation.web.builders.HttpSecurity;
+import org.springframework.security.core.authority.mapping.SimpleAuthorityMapper;
+import org.springframework.security.web.authentication.session.NullAuthenticatedSessionStrategy;
+
+@KeycloakConfiguration
+@ConditionalOnProperty(name = "keycloak.enabled", havingValue = "true", matchIfMissing = true)
+@EnableMethodSecurity
+@Import(KeycloakSpringBootConfigResolver.class)
+public class SecurityKeycloakConfig extends KeycloakWebSecurityConfigurerAdapter {
+
+ @Autowired
+ public void configureGlobal(AuthenticationManagerBuilder auth) {
+ final KeycloakAuthenticationProvider keycloakAuthenticationProvider =
+ keycloakAuthenticationProvider();
+ keycloakAuthenticationProvider.setGrantedAuthoritiesMapper(new SimpleAuthorityMapper());
+ auth.authenticationProvider(keycloakAuthenticationProvider);
+ }
+
+ @Bean
+ @Override
+ protected NullAuthenticatedSessionStrategy sessionAuthenticationStrategy() {
+ return new NullAuthenticatedSessionStrategy();
+ }
+
+ @Override
+ protected void configure(HttpSecurity http) throws Exception {
+ super.configure(http);
+ http
+ .csrf().disable()
+ .authorizeHttpRequests();
+ }
+}
diff --git a/src/main/java/org/fairdatatrain/trainhandler/config/WebConfig.java b/src/main/java/org/fairdatatrain/trainhandler/config/WebConfig.java
new file mode 100644
index 0000000..458a816
--- /dev/null
+++ b/src/main/java/org/fairdatatrain/trainhandler/config/WebConfig.java
@@ -0,0 +1,30 @@
+/**
+ * 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.trainhandler.config;
+
+import org.springframework.context.annotation.Configuration;
+import org.springframework.web.servlet.config.annotation.WebMvcConfigurer;
+
+@Configuration
+public class WebConfig implements WebMvcConfigurer {
+}
diff --git a/src/main/java/org/fairdatatrain/trainhandler/config/properties/DispatchProperties.java b/src/main/java/org/fairdatatrain/trainhandler/config/properties/DispatchProperties.java
new file mode 100644
index 0000000..784f8d7
--- /dev/null
+++ b/src/main/java/org/fairdatatrain/trainhandler/config/properties/DispatchProperties.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.trainhandler.config.properties;
+
+import lombok.AllArgsConstructor;
+import lombok.Getter;
+import lombok.NoArgsConstructor;
+import lombok.Setter;
+
+@NoArgsConstructor
+@AllArgsConstructor
+@Getter
+@Setter
+public class DispatchProperties {
+
+ private String callbackRoot;
+
+}
diff --git a/src/main/java/org/fairdatatrain/trainhandler/config/properties/PollingProperties.java b/src/main/java/org/fairdatatrain/trainhandler/config/properties/PollingProperties.java
new file mode 100644
index 0000000..25ece7f
--- /dev/null
+++ b/src/main/java/org/fairdatatrain/trainhandler/config/properties/PollingProperties.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.trainhandler.config.properties;
+
+import lombok.AllArgsConstructor;
+import lombok.Getter;
+import lombok.NoArgsConstructor;
+import lombok.Setter;
+
+import java.time.Duration;
+import java.time.Instant;
+import java.time.temporal.ChronoUnit;
+
+@NoArgsConstructor
+@AllArgsConstructor
+@Getter
+@Setter
+public class PollingProperties {
+
+ private Duration timeout = Duration.parse("PT50S");
+
+ public long getTimeoutMs() {
+ return timeout.toMillis();
+ }
+
+ public Instant getTimeoutForCurrentPoll() {
+ return Instant.now().plus(getTimeoutMs(), ChronoUnit.MILLIS);
+ }
+}
diff --git a/src/main/java/org/fairdatatrain/trainhandler/data/model/Job.java b/src/main/java/org/fairdatatrain/trainhandler/data/model/Job.java
new file mode 100644
index 0000000..112397e
--- /dev/null
+++ b/src/main/java/org/fairdatatrain/trainhandler/data/model/Job.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.trainhandler.data.model;
+
+import lombok.AllArgsConstructor;
+import lombok.Getter;
+import lombok.NoArgsConstructor;
+import lombok.Setter;
+import lombok.experimental.SuperBuilder;
+import org.fairdatatrain.trainhandler.data.model.base.BaseEntity;
+import org.fairdatatrain.trainhandler.data.model.enums.JobStatus;
+
+import javax.persistence.*;
+import javax.validation.constraints.NotNull;
+import java.sql.Timestamp;
+import java.util.List;
+
+@Entity(name = "Job")
+@Table(name = "job")
+@Getter
+@Setter
+@AllArgsConstructor
+@NoArgsConstructor
+@SuperBuilder
+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 = "version", nullable = false)
+ private Long version;
+
+ @NotNull
+ @ManyToOne
+ @JoinColumn(name = "plan_target_id")
+ private PlanTarget target;
+
+ @NotNull
+ @ManyToOne
+ @JoinColumn(name = "run_id", nullable = false)
+ private Run run;
+
+ @OneToMany(fetch = FetchType.LAZY, mappedBy = "job")
+ private List events;
+
+ @OneToMany(fetch = FetchType.LAZY, mappedBy = "job")
+ private List artifacts;
+
+ public boolean isFinished() {
+ return getStatus().equals(JobStatus.FAILED)
+ || getStatus().equals(JobStatus.FINISHED)
+ || getStatus().equals(JobStatus.ERRORED);
+ }
+}
diff --git a/src/main/java/org/fairdatatrain/trainhandler/data/model/JobArtifact.java b/src/main/java/org/fairdatatrain/trainhandler/data/model/JobArtifact.java
new file mode 100644
index 0000000..f25f1ac
--- /dev/null
+++ b/src/main/java/org/fairdatatrain/trainhandler/data/model/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.trainhandler.data.model;
+
+import lombok.AllArgsConstructor;
+import lombok.Getter;
+import lombok.NoArgsConstructor;
+import lombok.Setter;
+import lombok.experimental.SuperBuilder;
+import org.fairdatatrain.trainhandler.data.model.base.BaseEntity;
+import org.fairdatatrain.trainhandler.data.model.enums.ArtifactStorage;
+import org.hibernate.annotations.Type;
+
+import javax.persistence.*;
+import javax.validation.constraints.NotNull;
+import java.sql.Timestamp;
+
+@Entity(name = "JobArtifact")
+@Table(name = "job_artifact")
+@Getter
+@Setter
+@AllArgsConstructor
+@NoArgsConstructor
+@SuperBuilder
+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;
+
+ @Lob
+ @Column(name = "data")
+ @Type(type = "org.hibernate.type.BinaryType")
+ private byte[] data;
+
+ @NotNull
+ @ManyToOne
+ @JoinColumn(name = "job_id", nullable = false)
+ private Job job;
+
+}
diff --git a/src/main/java/org/fairdatatrain/trainhandler/data/model/JobEvent.java b/src/main/java/org/fairdatatrain/trainhandler/data/model/JobEvent.java
new file mode 100644
index 0000000..9fda6a6
--- /dev/null
+++ b/src/main/java/org/fairdatatrain/trainhandler/data/model/JobEvent.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.trainhandler.data.model;
+
+import lombok.AllArgsConstructor;
+import lombok.Getter;
+import lombok.NoArgsConstructor;
+import lombok.Setter;
+import lombok.experimental.SuperBuilder;
+import org.fairdatatrain.trainhandler.data.model.base.BaseEntity;
+import org.fairdatatrain.trainhandler.data.model.enums.JobStatus;
+
+import javax.persistence.*;
+import javax.validation.constraints.NotNull;
+import java.sql.Timestamp;
+
+@Entity(name = "JobEvent")
+@Table(name = "job_event")
+@Getter
+@Setter
+@AllArgsConstructor
+@NoArgsConstructor
+@SuperBuilder
+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;
+}
diff --git a/src/main/java/org/fairdatatrain/trainhandler/data/model/Plan.java b/src/main/java/org/fairdatatrain/trainhandler/data/model/Plan.java
new file mode 100644
index 0000000..4aea02c
--- /dev/null
+++ b/src/main/java/org/fairdatatrain/trainhandler/data/model/Plan.java
@@ -0,0 +1,69 @@
+/**
+ * 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.trainhandler.data.model;
+
+import lombok.AllArgsConstructor;
+import lombok.Getter;
+import lombok.NoArgsConstructor;
+import lombok.Setter;
+import lombok.experimental.SuperBuilder;
+import org.fairdatatrain.trainhandler.data.model.base.BaseEntity;
+
+import javax.persistence.*;
+import javax.validation.constraints.NotBlank;
+import javax.validation.constraints.NotNull;
+import java.util.List;
+
+@Entity(name = "Plan")
+@Table(name = "plan")
+@Getter
+@Setter
+@AllArgsConstructor
+@NoArgsConstructor
+@SuperBuilder
+public class Plan extends BaseEntity {
+
+ @NotBlank
+ @NotNull
+ @Column(name = "display_name", nullable = false)
+ private String displayName;
+
+ @NotNull
+ @Column(name = "note", nullable = false)
+ private String note;
+
+ @NotNull
+ @Column(name = "publish_artifacts", nullable = false)
+ private Boolean publishArtifacts;
+
+ @NotNull
+ @ManyToOne
+ @JoinColumn(name = "train_id")
+ private Train train;
+
+ @OneToMany(fetch = FetchType.LAZY, mappedBy = "plan")
+ private List targets;
+
+ @OneToMany(fetch = FetchType.LAZY, mappedBy = "plan")
+ private List runs;
+}
diff --git a/src/main/java/org/fairdatatrain/trainhandler/data/model/PlanTarget.java b/src/main/java/org/fairdatatrain/trainhandler/data/model/PlanTarget.java
new file mode 100644
index 0000000..152648a
--- /dev/null
+++ b/src/main/java/org/fairdatatrain/trainhandler/data/model/PlanTarget.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.trainhandler.data.model;
+
+import lombok.AllArgsConstructor;
+import lombok.Getter;
+import lombok.NoArgsConstructor;
+import lombok.Setter;
+import lombok.experimental.SuperBuilder;
+import org.fairdatatrain.trainhandler.data.model.base.BaseEntity;
+
+import javax.persistence.*;
+import javax.validation.constraints.NotNull;
+import java.util.UUID;
+
+@Entity(name = "PlanTarget")
+@Table(name = "plan_target")
+@Getter
+@Setter
+@AllArgsConstructor
+@NoArgsConstructor
+@SuperBuilder
+public class PlanTarget extends BaseEntity {
+
+ @NotNull
+ @ManyToOne
+ @JoinColumn(name = "station_id")
+ private Station station;
+
+ @NotNull
+ @ManyToOne
+ @JoinColumn(name = "plan_id")
+ private Plan plan;
+
+ @NotNull
+ @Column(name = "publish_artifacts", nullable = false)
+ private Boolean publishArtifacts;
+
+ public UUID getStationUuid() {
+ return station.getUuid();
+ }
+}
diff --git a/src/main/java/org/fairdatatrain/trainhandler/data/model/Run.java b/src/main/java/org/fairdatatrain/trainhandler/data/model/Run.java
new file mode 100644
index 0000000..75e856e
--- /dev/null
+++ b/src/main/java/org/fairdatatrain/trainhandler/data/model/Run.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.trainhandler.data.model;
+
+import lombok.AllArgsConstructor;
+import lombok.Getter;
+import lombok.NoArgsConstructor;
+import lombok.Setter;
+import lombok.experimental.SuperBuilder;
+import org.fairdatatrain.trainhandler.data.model.base.BaseEntity;
+import org.fairdatatrain.trainhandler.data.model.enums.RunStatus;
+
+import javax.persistence.*;
+import javax.validation.constraints.NotBlank;
+import javax.validation.constraints.NotNull;
+import java.sql.Timestamp;
+import java.util.List;
+
+@Entity(name = "Run")
+@Table(name = "run")
+@Getter
+@Setter
+@AllArgsConstructor
+@NoArgsConstructor
+@SuperBuilder
+public class Run extends BaseEntity {
+
+ @NotBlank
+ @NotNull
+ @Column(name = "display_name", nullable = false)
+ private String displayName;
+
+ @NotNull
+ @Column(name = "note", nullable = false)
+ private String note;
+
+ @Enumerated(EnumType.STRING)
+ @Column(name = "status", columnDefinition = "run_status", nullable = false)
+ private RunStatus status;
+
+ @Column(name = "should_start_at")
+ private Timestamp shouldStartAt;
+
+ @Column(name = "started_at")
+ private Timestamp startedAt;
+
+ @Column(name = "finished_at")
+ private Timestamp finishedAt;
+
+ @Column(name = "version", nullable = false)
+ private Long version;
+
+ @NotNull
+ @ManyToOne
+ @JoinColumn(name = "plan_id")
+ private Plan plan;
+
+ @OneToMany(fetch = FetchType.EAGER, mappedBy = "run")
+ private List jobs;
+
+ public boolean isFinished() {
+ return getStatus().equals(RunStatus.FAILED)
+ || getStatus().equals(RunStatus.FINISHED)
+ || getStatus().equals(RunStatus.ERRORED);
+ }
+}
diff --git a/src/main/java/org/fairdatatrain/trainhandler/data/model/Station.java b/src/main/java/org/fairdatatrain/trainhandler/data/model/Station.java
new file mode 100644
index 0000000..6e089df
--- /dev/null
+++ b/src/main/java/org/fairdatatrain/trainhandler/data/model/Station.java
@@ -0,0 +1,97 @@
+/**
+ * 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.trainhandler.data.model;
+
+import lombok.AllArgsConstructor;
+import lombok.Getter;
+import lombok.NoArgsConstructor;
+import lombok.Setter;
+import lombok.experimental.SuperBuilder;
+import org.fairdatatrain.trainhandler.data.model.base.BaseEntity;
+import org.fairdatatrain.trainhandler.data.model.enums.SyncItemStatus;
+
+import javax.persistence.*;
+import javax.validation.constraints.NotBlank;
+import javax.validation.constraints.NotNull;
+import java.sql.Timestamp;
+import java.util.List;
+
+@Entity(name = "Station")
+@Table(name = "station")
+@Getter
+@Setter
+@AllArgsConstructor
+@NoArgsConstructor
+@SuperBuilder(toBuilder = true)
+public class Station extends BaseEntity {
+
+ @NotBlank
+ @NotNull
+ @Column(name = "uri", nullable = false)
+ private String uri;
+
+ @NotBlank
+ @NotNull
+ @Column(name = "title", nullable = false)
+ private String title;
+
+ @NotNull
+ @Column(name = "description", nullable = false)
+ private String description;
+
+ @NotNull
+ @Column(name = "keywords", nullable = false)
+ private String keywords;
+
+ @NotNull
+ @Column(name = "endpoint_url", nullable = false)
+ private String endpointUrl;
+
+ @NotNull
+ @Column(name = "endpoint_description", nullable = false)
+ private String endpointDescription;
+
+ @Column(name = "metadata")
+ private String metadata;
+
+ @Column(name = "soft_deleted")
+ private Boolean softDeleted;
+
+ @Enumerated(EnumType.STRING)
+ @Column(name = "status", columnDefinition = "sync_item_status", nullable = false)
+ private SyncItemStatus status;
+
+ @Column(name = "last_contact_at")
+ private Timestamp lastContactAt;
+
+ @ManyToOne
+ @JoinColumn(name = "station_directory_id")
+ private StationDirectory directory;
+
+ @ManyToMany(fetch = FetchType.LAZY, cascade = CascadeType.ALL)
+ @JoinTable(
+ name = "station_train_type",
+ joinColumns = @JoinColumn(name = "station_id"),
+ inverseJoinColumns = @JoinColumn(name = "train_type_id"))
+ private List types;
+}
diff --git a/src/main/java/org/fairdatatrain/trainhandler/data/model/StationDirectory.java b/src/main/java/org/fairdatatrain/trainhandler/data/model/StationDirectory.java
new file mode 100644
index 0000000..ef1b673
--- /dev/null
+++ b/src/main/java/org/fairdatatrain/trainhandler/data/model/StationDirectory.java
@@ -0,0 +1,89 @@
+/**
+ * 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.trainhandler.data.model;
+
+import lombok.AllArgsConstructor;
+import lombok.Getter;
+import lombok.NoArgsConstructor;
+import lombok.Setter;
+import lombok.experimental.SuperBuilder;
+import org.fairdatatrain.trainhandler.data.model.base.BaseEntity;
+import org.fairdatatrain.trainhandler.data.model.enums.SyncServiceStatus;
+
+import javax.persistence.*;
+import javax.validation.constraints.NotBlank;
+import javax.validation.constraints.NotNull;
+import java.sql.Timestamp;
+import java.util.ArrayList;
+import java.util.List;
+
+@Entity(name = "StationDirectory")
+@Table(name = "station_directory")
+@SuperBuilder
+@Getter
+@Setter
+@AllArgsConstructor
+@NoArgsConstructor
+public class StationDirectory extends BaseEntity {
+
+ @NotBlank
+ @NotNull
+ @Column(name = "uri", nullable = false)
+ private String uri;
+
+ @NotBlank
+ @NotNull
+ @Column(name = "display_name", nullable = false)
+ private String displayName;
+
+ @NotNull
+ @Column(name = "note", nullable = false)
+ private String note;
+
+ @Column(name = "metadata")
+ private String metadata;
+
+ @Enumerated(EnumType.STRING)
+ @Column(name = "status", columnDefinition = "sync_service_status", nullable = false)
+ private SyncServiceStatus status;
+
+ @Column(name = "last_contact_at")
+ private Timestamp lastContactAt;
+
+ @OneToMany(fetch = FetchType.LAZY, mappedBy = "directory")
+ private List stations;
+
+ public List getStations() {
+ if (stations == null) {
+ stations = new ArrayList<>();
+ }
+ return stations;
+ }
+
+ public Boolean isDeletable() {
+ if (getStatus().equals(SyncServiceStatus.SYNCING)) {
+ return false;
+ }
+ return getStations().isEmpty();
+ }
+}
diff --git a/src/main/java/org/fairdatatrain/trainhandler/data/model/Train.java b/src/main/java/org/fairdatatrain/trainhandler/data/model/Train.java
new file mode 100644
index 0000000..f1c7074
--- /dev/null
+++ b/src/main/java/org/fairdatatrain/trainhandler/data/model/Train.java
@@ -0,0 +1,89 @@
+/**
+ * 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.trainhandler.data.model;
+
+import lombok.AllArgsConstructor;
+import lombok.Getter;
+import lombok.NoArgsConstructor;
+import lombok.Setter;
+import lombok.experimental.SuperBuilder;
+import org.fairdatatrain.trainhandler.data.model.base.BaseEntity;
+import org.fairdatatrain.trainhandler.data.model.enums.SyncItemStatus;
+
+import javax.persistence.*;
+import javax.validation.constraints.NotBlank;
+import javax.validation.constraints.NotNull;
+import java.sql.Timestamp;
+import java.util.List;
+
+@Entity(name = "Train")
+@Table(name = "train")
+@Getter
+@Setter
+@AllArgsConstructor
+@NoArgsConstructor
+@SuperBuilder(toBuilder = true)
+public class Train extends BaseEntity {
+
+ @NotBlank
+ @NotNull
+ @Column(name = "uri", nullable = false)
+ private String uri;
+
+ @NotBlank
+ @NotNull
+ @Column(name = "title", nullable = false)
+ private String title;
+
+ @NotNull
+ @Column(name = "description", nullable = false)
+ private String description;
+
+ @NotNull
+ @Column(name = "keywords", nullable = false)
+ private String keywords;
+
+ @Column(name = "metadata")
+ private String metadata;
+
+ @Column(name = "soft_deleted")
+ private Boolean softDeleted;
+
+ @Enumerated(EnumType.STRING)
+ @Column(name = "status", columnDefinition = "sync_item_status", nullable = false)
+ private SyncItemStatus status;
+
+ @ManyToOne
+ @JoinColumn(name = "train_garage_id")
+ private TrainGarage garage;
+
+ @Column(name = "last_contact_at")
+ private Timestamp lastContactAt;
+
+ @ManyToMany(fetch = FetchType.LAZY, cascade = CascadeType.ALL)
+ @JoinTable(
+ name = "train_train_type",
+ joinColumns = @JoinColumn(name = "train_id"),
+ inverseJoinColumns = @JoinColumn(name = "train_type_id"))
+ private List types;
+}
diff --git a/src/main/java/org/fairdatatrain/trainhandler/data/model/TrainGarage.java b/src/main/java/org/fairdatatrain/trainhandler/data/model/TrainGarage.java
new file mode 100644
index 0000000..7073bc1
--- /dev/null
+++ b/src/main/java/org/fairdatatrain/trainhandler/data/model/TrainGarage.java
@@ -0,0 +1,89 @@
+/**
+ * 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.trainhandler.data.model;
+
+import lombok.AllArgsConstructor;
+import lombok.Getter;
+import lombok.NoArgsConstructor;
+import lombok.Setter;
+import lombok.experimental.SuperBuilder;
+import org.fairdatatrain.trainhandler.data.model.base.BaseEntity;
+import org.fairdatatrain.trainhandler.data.model.enums.SyncServiceStatus;
+
+import javax.persistence.*;
+import javax.validation.constraints.NotBlank;
+import javax.validation.constraints.NotNull;
+import java.sql.Timestamp;
+import java.util.ArrayList;
+import java.util.List;
+
+@Entity(name = "TrainGarage")
+@Table(name = "train_garage")
+@Getter
+@Setter
+@AllArgsConstructor
+@NoArgsConstructor
+@SuperBuilder
+public class TrainGarage extends BaseEntity {
+
+ @NotBlank
+ @NotNull
+ @Column(name = "uri", nullable = false)
+ private String uri;
+
+ @NotBlank
+ @NotNull
+ @Column(name = "display_name", nullable = false)
+ private String displayName;
+
+ @NotNull
+ @Column(name = "note", nullable = false)
+ private String note;
+
+ @Column(name = "metadata")
+ private String metadata;
+
+ @Enumerated(EnumType.STRING)
+ @Column(name = "status", columnDefinition = "sync_service_status", nullable = false)
+ private SyncServiceStatus status;
+
+ @Column(name = "last_contact_at")
+ private Timestamp lastContactAt;
+
+ @OneToMany(fetch = FetchType.LAZY, mappedBy = "garage")
+ private List trains;
+
+ public List getTrains() {
+ if (trains == null) {
+ trains = new ArrayList<>();
+ }
+ return trains;
+ }
+
+ public Boolean isDeletable() {
+ if (getStatus().equals(SyncServiceStatus.SYNCING)) {
+ return false;
+ }
+ return getTrains().isEmpty();
+ }
+}
diff --git a/src/main/java/org/fairdatatrain/trainhandler/data/model/TrainType.java b/src/main/java/org/fairdatatrain/trainhandler/data/model/TrainType.java
new file mode 100644
index 0000000..8b20494
--- /dev/null
+++ b/src/main/java/org/fairdatatrain/trainhandler/data/model/TrainType.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.trainhandler.data.model;
+
+import lombok.AllArgsConstructor;
+import lombok.Getter;
+import lombok.NoArgsConstructor;
+import lombok.Setter;
+import lombok.experimental.SuperBuilder;
+import org.fairdatatrain.trainhandler.data.model.base.BaseEntity;
+
+import javax.persistence.*;
+import javax.validation.constraints.NotBlank;
+import javax.validation.constraints.NotNull;
+import java.util.List;
+
+@Entity(name = "TrainType")
+@Table(name = "train_type")
+@Getter
+@Setter
+@AllArgsConstructor
+@NoArgsConstructor
+@SuperBuilder
+public class TrainType extends BaseEntity {
+
+ @NotBlank
+ @Column(name = "uri", nullable = false)
+ private String uri;
+
+ @NotBlank
+ @Column(name = "title", nullable = false)
+ private String title;
+
+ @NotNull
+ @Column(name = "note", nullable = false)
+ private String note;
+
+ @ManyToMany(mappedBy = "types", fetch = FetchType.LAZY)
+ private List trains;
+
+ @ManyToMany(mappedBy = "types", fetch = FetchType.LAZY)
+ private List stations;
+}
diff --git a/src/main/java/org/fairdatatrain/trainhandler/data/model/base/BaseEntity.java b/src/main/java/org/fairdatatrain/trainhandler/data/model/base/BaseEntity.java
new file mode 100644
index 0000000..649c14a
--- /dev/null
+++ b/src/main/java/org/fairdatatrain/trainhandler/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.trainhandler.data.model.base;
+
+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 javax.persistence.*;
+import javax.validation.constraints.NotNull;
+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/trainhandler/data/model/enums/ArtifactStorage.java b/src/main/java/org/fairdatatrain/trainhandler/data/model/enums/ArtifactStorage.java
new file mode 100644
index 0000000..b9cf6e9
--- /dev/null
+++ b/src/main/java/org/fairdatatrain/trainhandler/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.trainhandler.data.model.enums;
+
+public enum ArtifactStorage {
+ POSTGRES,
+ S3,
+ LOCALFS
+}
diff --git a/src/main/java/org/fairdatatrain/trainhandler/data/model/enums/JobStatus.java b/src/main/java/org/fairdatatrain/trainhandler/data/model/enums/JobStatus.java
new file mode 100644
index 0000000..dc151ea
--- /dev/null
+++ b/src/main/java/org/fairdatatrain/trainhandler/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.trainhandler.data.model.enums;
+
+public enum JobStatus {
+ PREPARED,
+ QUEUED,
+ RUNNING,
+ FINISHED,
+ ABORTING,
+ ABORTED,
+ ERRORED,
+ FAILED
+}
diff --git a/src/main/java/org/fairdatatrain/trainhandler/data/model/enums/RunStatus.java b/src/main/java/org/fairdatatrain/trainhandler/data/model/enums/RunStatus.java
new file mode 100644
index 0000000..8002b3e
--- /dev/null
+++ b/src/main/java/org/fairdatatrain/trainhandler/data/model/enums/RunStatus.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.trainhandler.data.model.enums;
+
+public enum RunStatus {
+ SCHEDULED,
+ PREPARED,
+ RUNNING,
+ FINISHED,
+ ABORTING,
+ ABORTED,
+ ERRORED,
+ FAILED
+}
diff --git a/src/main/java/org/fairdatatrain/trainhandler/data/model/enums/SyncItemStatus.java b/src/main/java/org/fairdatatrain/trainhandler/data/model/enums/SyncItemStatus.java
new file mode 100644
index 0000000..ab2629a
--- /dev/null
+++ b/src/main/java/org/fairdatatrain/trainhandler/data/model/enums/SyncItemStatus.java
@@ -0,0 +1,28 @@
+/**
+ * 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.trainhandler.data.model.enums;
+
+public enum SyncItemStatus {
+ SYNCED,
+ DELETED
+}
diff --git a/src/main/java/org/fairdatatrain/trainhandler/data/model/enums/SyncServiceStatus.java b/src/main/java/org/fairdatatrain/trainhandler/data/model/enums/SyncServiceStatus.java
new file mode 100644
index 0000000..c4c7170
--- /dev/null
+++ b/src/main/java/org/fairdatatrain/trainhandler/data/model/enums/SyncServiceStatus.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.trainhandler.data.model.enums;
+
+public enum SyncServiceStatus {
+ SYNCED,
+ SYNCING,
+ UNREACHABLE,
+ INVALID,
+ DELETED
+}
diff --git a/src/main/java/org/fairdatatrain/trainhandler/data/repository/JobArtifactRepository.java b/src/main/java/org/fairdatatrain/trainhandler/data/repository/JobArtifactRepository.java
new file mode 100644
index 0000000..7ab600b
--- /dev/null
+++ b/src/main/java/org/fairdatatrain/trainhandler/data/repository/JobArtifactRepository.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.trainhandler.data.repository;
+
+import org.fairdatatrain.trainhandler.data.model.JobArtifact;
+import org.fairdatatrain.trainhandler.data.repository.base.BaseRepository;
+import org.springframework.data.jpa.repository.Query;
+import org.springframework.stereotype.Repository;
+
+import java.util.List;
+import java.util.UUID;
+
+@Repository
+public interface JobArtifactRepository extends BaseRepository {
+
+ @Query(
+ """
+ SELECT ja
+ FROM JobArtifact ja
+ JOIN Job j ON ja.job = j
+ JOIN PlanTarget pt ON j.target = pt
+ JOIN Plan p ON pt.plan = p
+ WHERE p.uuid = :planUuid
+ AND p.publishArtifacts = TRUE
+ AND pt.publishArtifacts = TRUE
+ """
+ )
+ List getPublicJobArtifactsOfPlan(UUID planUuid);
+
+ @Query(
+ """
+ SELECT ja
+ FROM JobArtifact ja
+ JOIN Job j ON ja.job = j
+ JOIN PlanTarget pt ON j.target = pt
+ JOIN Plan p ON pt.plan = p
+ WHERE pt.uuid = :targetUuid
+ AND p.publishArtifacts = TRUE
+ AND pt.publishArtifacts = TRUE
+ """
+ )
+ List getPublicJobArtifactsOfTarget(UUID targetUuid);
+}
diff --git a/src/main/java/org/fairdatatrain/trainhandler/data/repository/JobEventRepository.java b/src/main/java/org/fairdatatrain/trainhandler/data/repository/JobEventRepository.java
new file mode 100644
index 0000000..6b40333
--- /dev/null
+++ b/src/main/java/org/fairdatatrain/trainhandler/data/repository/JobEventRepository.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.trainhandler.data.repository;
+
+import org.fairdatatrain.trainhandler.data.model.Job;
+import org.fairdatatrain.trainhandler.data.model.JobEvent;
+import org.fairdatatrain.trainhandler.data.repository.base.BaseRepository;
+import org.springframework.stereotype.Repository;
+
+import java.sql.Timestamp;
+import java.util.List;
+
+@Repository
+public interface JobEventRepository extends BaseRepository {
+
+ List findAllByJobOrderByOccurredAtAsc(Job job);
+
+ List findAllByJobAndOccurredAtAfterOrderByOccurredAtAsc(Job job, Timestamp threshold);
+}
diff --git a/src/main/java/org/fairdatatrain/trainhandler/data/repository/JobRepository.java b/src/main/java/org/fairdatatrain/trainhandler/data/repository/JobRepository.java
new file mode 100644
index 0000000..e870830
--- /dev/null
+++ b/src/main/java/org/fairdatatrain/trainhandler/data/repository/JobRepository.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.trainhandler.data.repository;
+
+import org.fairdatatrain.trainhandler.data.model.Job;
+import org.fairdatatrain.trainhandler.data.repository.base.BaseRepository;
+import org.springframework.data.domain.Page;
+import org.springframework.data.domain.Pageable;
+import org.springframework.stereotype.Repository;
+
+import java.util.UUID;
+
+@Repository
+public interface JobRepository extends BaseRepository {
+
+ Page findAllByRunUuid(UUID runUuid, Pageable pageable);
+
+}
diff --git a/src/main/java/org/fairdatatrain/trainhandler/data/repository/PlanRepository.java b/src/main/java/org/fairdatatrain/trainhandler/data/repository/PlanRepository.java
new file mode 100644
index 0000000..ec4ecf7
--- /dev/null
+++ b/src/main/java/org/fairdatatrain/trainhandler/data/repository/PlanRepository.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.trainhandler.data.repository;
+
+import org.fairdatatrain.trainhandler.data.model.Plan;
+import org.fairdatatrain.trainhandler.data.repository.base.BaseRepository;
+import org.springframework.data.domain.Page;
+import org.springframework.data.domain.Pageable;
+import org.springframework.stereotype.Repository;
+
+@Repository
+public interface PlanRepository extends BaseRepository {
+
+ Page findByDisplayNameContainingIgnoreCase(String query, Pageable pageable);
+}
diff --git a/src/main/java/org/fairdatatrain/trainhandler/data/repository/PlanTargetRepository.java b/src/main/java/org/fairdatatrain/trainhandler/data/repository/PlanTargetRepository.java
new file mode 100644
index 0000000..7cefd36
--- /dev/null
+++ b/src/main/java/org/fairdatatrain/trainhandler/data/repository/PlanTargetRepository.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.trainhandler.data.repository;
+
+import org.fairdatatrain.trainhandler.data.model.PlanTarget;
+import org.fairdatatrain.trainhandler.data.repository.base.BaseRepository;
+import org.springframework.stereotype.Repository;
+
+@Repository
+public interface PlanTargetRepository extends BaseRepository {
+}
diff --git a/src/main/java/org/fairdatatrain/trainhandler/data/repository/RunRepository.java b/src/main/java/org/fairdatatrain/trainhandler/data/repository/RunRepository.java
new file mode 100644
index 0000000..3047e4d
--- /dev/null
+++ b/src/main/java/org/fairdatatrain/trainhandler/data/repository/RunRepository.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.trainhandler.data.repository;
+
+import org.fairdatatrain.trainhandler.data.model.Run;
+import org.fairdatatrain.trainhandler.data.repository.base.BaseRepository;
+import org.springframework.data.domain.Page;
+import org.springframework.data.domain.Pageable;
+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.UUID;
+
+@Repository
+public interface RunRepository extends BaseRepository {
+
+ Page findAllByPlanUuid(UUID planUuid, Pageable pageable);
+
+ @Query("""
+ SELECT r
+ FROM Run r
+ WHERE
+ (
+ r.status = 'SCHEDULED'
+ AND r.shouldStartAt IS NOT NULL
+ AND r.shouldStartAt < :timestamp
+ ) OR (
+ r.status = 'PREPARED'
+ AND r.startedAt IS NULL
+ AND r.shouldStartAt IS NULL
+ )
+ ORDER BY r.shouldStartAt ASC
+ """)
+ Page findRunToDispatch(@Param("timestamp") Timestamp timestamp, Pageable pageable);
+}
diff --git a/src/main/java/org/fairdatatrain/trainhandler/data/repository/StationDirectoryRepository.java b/src/main/java/org/fairdatatrain/trainhandler/data/repository/StationDirectoryRepository.java
new file mode 100644
index 0000000..cc7e656
--- /dev/null
+++ b/src/main/java/org/fairdatatrain/trainhandler/data/repository/StationDirectoryRepository.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.trainhandler.data.repository;
+
+import org.fairdatatrain.trainhandler.data.model.StationDirectory;
+import org.fairdatatrain.trainhandler.data.repository.base.BaseRepository;
+import org.springframework.data.domain.Page;
+import org.springframework.data.domain.Pageable;
+import org.springframework.stereotype.Repository;
+
+@Repository
+public interface StationDirectoryRepository extends BaseRepository {
+
+ Page findByDisplayNameContainingIgnoreCase(String query, Pageable pageable);
+}
diff --git a/src/main/java/org/fairdatatrain/trainhandler/data/repository/StationRepository.java b/src/main/java/org/fairdatatrain/trainhandler/data/repository/StationRepository.java
new file mode 100644
index 0000000..2d26f3b
--- /dev/null
+++ b/src/main/java/org/fairdatatrain/trainhandler/data/repository/StationRepository.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.trainhandler.data.repository;
+
+import org.fairdatatrain.trainhandler.data.model.Station;
+import org.fairdatatrain.trainhandler.data.repository.base.BaseRepository;
+import org.springframework.data.domain.Page;
+import org.springframework.data.domain.Pageable;
+import org.springframework.data.jpa.repository.Query;
+import org.springframework.data.repository.query.Param;
+import org.springframework.stereotype.Repository;
+
+import java.util.List;
+
+@Repository
+public interface StationRepository extends BaseRepository {
+
+ Page findAllBySoftDeletedIsFalse(Pageable pageable);
+
+ Page findByTitleContainingIgnoreCaseAndSoftDeletedIsFalse(
+ String query, Pageable pageable
+ );
+
+ @Query("""
+ SELECT s
+ FROM Station s
+ WHERE LOWER(s.title) LIKE %:query%
+ AND s.softDeleted IS FALSE
+ ORDER BY s.title""")
+ List findByTitleContainingIgnoreCase(@Param("query") String query);
+}
diff --git a/src/main/java/org/fairdatatrain/trainhandler/data/repository/TrainGarageRepository.java b/src/main/java/org/fairdatatrain/trainhandler/data/repository/TrainGarageRepository.java
new file mode 100644
index 0000000..7aa0638
--- /dev/null
+++ b/src/main/java/org/fairdatatrain/trainhandler/data/repository/TrainGarageRepository.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.trainhandler.data.repository;
+
+import org.fairdatatrain.trainhandler.data.model.TrainGarage;
+import org.fairdatatrain.trainhandler.data.repository.base.BaseRepository;
+import org.springframework.data.domain.Page;
+import org.springframework.data.domain.Pageable;
+import org.springframework.stereotype.Repository;
+
+@Repository
+public interface TrainGarageRepository extends BaseRepository {
+
+ Page findByDisplayNameContainingIgnoreCase(String query, Pageable pageable);
+}
diff --git a/src/main/java/org/fairdatatrain/trainhandler/data/repository/TrainRepository.java b/src/main/java/org/fairdatatrain/trainhandler/data/repository/TrainRepository.java
new file mode 100644
index 0000000..b9fac9a
--- /dev/null
+++ b/src/main/java/org/fairdatatrain/trainhandler/data/repository/TrainRepository.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.trainhandler.data.repository;
+
+import org.fairdatatrain.trainhandler.data.model.Train;
+import org.fairdatatrain.trainhandler.data.repository.base.BaseRepository;
+import org.springframework.data.domain.Page;
+import org.springframework.data.domain.Pageable;
+import org.springframework.data.jpa.repository.Query;
+import org.springframework.data.repository.query.Param;
+import org.springframework.stereotype.Repository;
+
+import java.util.List;
+
+@Repository
+public interface TrainRepository extends BaseRepository {
+
+ Page findAllBySoftDeletedIsFalse(Pageable pageable);
+
+ Page findByTitleContainingIgnoreCaseAndSoftDeletedIsFalse(
+ String query, Pageable pageable
+ );
+
+ @Query("""
+ SELECT t
+ FROM Train t
+ WHERE LOWER(t.title) LIKE %:query%
+ AND t.softDeleted IS FALSE
+ ORDER BY t.title""")
+ List findByTitleContainingIgnoreCase(@Param("query") String query);
+}
diff --git a/src/main/java/org/fairdatatrain/trainhandler/data/repository/TrainTypeRepository.java b/src/main/java/org/fairdatatrain/trainhandler/data/repository/TrainTypeRepository.java
new file mode 100644
index 0000000..9641abc
--- /dev/null
+++ b/src/main/java/org/fairdatatrain/trainhandler/data/repository/TrainTypeRepository.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.trainhandler.data.repository;
+
+import org.fairdatatrain.trainhandler.data.model.TrainType;
+import org.fairdatatrain.trainhandler.data.repository.base.BaseRepository;
+import org.springframework.data.domain.Page;
+import org.springframework.data.domain.Pageable;
+import org.springframework.stereotype.Repository;
+
+@Repository
+public interface TrainTypeRepository extends BaseRepository {
+
+ Page findByTitleContainingIgnoreCase(String query, Pageable pageable);
+}
diff --git a/src/main/java/org/fairdatatrain/trainhandler/data/repository/base/BaseRepository.java b/src/main/java/org/fairdatatrain/trainhandler/data/repository/base/BaseRepository.java
new file mode 100644
index 0000000..ca78e8f
--- /dev/null
+++ b/src/main/java/org/fairdatatrain/trainhandler/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.trainhandler.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/trainhandler/exception/CannotPerformException.java b/src/main/java/org/fairdatatrain/trainhandler/exception/CannotPerformException.java
new file mode 100644
index 0000000..af30e1d
--- /dev/null
+++ b/src/main/java/org/fairdatatrain/trainhandler/exception/CannotPerformException.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.trainhandler.exception;
+
+import lombok.AllArgsConstructor;
+import lombok.Getter;
+
+import java.util.Map;
+import java.util.UUID;
+
+@AllArgsConstructor
+@Getter
+public class CannotPerformException extends Exception {
+
+ private final String entityName;
+
+ private final String operation;
+
+ private final Map fields;
+
+ public CannotPerformException(String entityName, UUID uuid, String operation, String message) {
+ this.entityName = entityName;
+ this.operation = operation;
+ this.fields = Map.of("uuid", uuid.toString(), "info", message);
+ }
+}
diff --git a/src/main/java/org/fairdatatrain/trainhandler/exception/JobSecurityException.java b/src/main/java/org/fairdatatrain/trainhandler/exception/JobSecurityException.java
new file mode 100644
index 0000000..9e2e2fa
--- /dev/null
+++ b/src/main/java/org/fairdatatrain/trainhandler/exception/JobSecurityException.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.trainhandler.exception;
+
+import lombok.AllArgsConstructor;
+import lombok.Getter;
+
+@AllArgsConstructor
+@Getter
+public class JobSecurityException extends Exception {
+
+ private final String message;
+}
diff --git a/src/main/java/org/fairdatatrain/trainhandler/exception/NotFoundException.java b/src/main/java/org/fairdatatrain/trainhandler/exception/NotFoundException.java
new file mode 100644
index 0000000..a0c4420
--- /dev/null
+++ b/src/main/java/org/fairdatatrain/trainhandler/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.trainhandler.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/trainhandler/exception/NotImplementedException.java b/src/main/java/org/fairdatatrain/trainhandler/exception/NotImplementedException.java
new file mode 100644
index 0000000..a3ac0c4
--- /dev/null
+++ b/src/main/java/org/fairdatatrain/trainhandler/exception/NotImplementedException.java
@@ -0,0 +1,26 @@
+/**
+ * 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.trainhandler.exception;
+
+public class NotImplementedException extends Exception {
+}
diff --git a/src/main/java/org/fairdatatrain/trainhandler/service/actuator/AppInfoContributor.java b/src/main/java/org/fairdatatrain/trainhandler/service/actuator/AppInfoContributor.java
new file mode 100644
index 0000000..b1a59fd
--- /dev/null
+++ b/src/main/java/org/fairdatatrain/trainhandler/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.trainhandler.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 Point");
+ 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/trainhandler/service/async/AsyncEventPublisher.java b/src/main/java/org/fairdatatrain/trainhandler/service/async/AsyncEventPublisher.java
new file mode 100644
index 0000000..1c958e0
--- /dev/null
+++ b/src/main/java/org/fairdatatrain/trainhandler/service/async/AsyncEventPublisher.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.trainhandler.service.async;
+
+import lombok.AllArgsConstructor;
+import lombok.extern.slf4j.Slf4j;
+import org.fairdatatrain.trainhandler.api.dto.job.JobDTO;
+import org.fairdatatrain.trainhandler.api.dto.run.RunDTO;
+import org.springframework.context.ApplicationEventPublisher;
+import org.springframework.stereotype.Component;
+
+import static java.lang.String.format;
+
+@Component
+@AllArgsConstructor
+@Slf4j
+public class AsyncEventPublisher {
+
+ private final ApplicationEventPublisher publisher;
+
+ public void publishNewJobEventNotification(final RunDTO run, final JobDTO job) {
+ log.info(format(
+ "Publishing new job notification for job %s (run %s)",
+ job.getUuid(), run.getUuid()
+ ));
+ publisher.publishEvent(new JobNotification(this, run, job));
+ }
+
+ public void publishNewJobEventNotification(final RunDTO run) {
+ log.info(format(
+ "Publishing new run notification for run %s",
+ run.getUuid()
+ ));
+ publisher.publishEvent(new JobNotification(this, run, null));
+ }
+
+}
diff --git a/src/main/java/org/fairdatatrain/trainhandler/service/async/JobNotification.java b/src/main/java/org/fairdatatrain/trainhandler/service/async/JobNotification.java
new file mode 100644
index 0000000..89379dd
--- /dev/null
+++ b/src/main/java/org/fairdatatrain/trainhandler/service/async/JobNotification.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.trainhandler.service.async;
+
+import org.fairdatatrain.trainhandler.api.dto.job.JobDTO;
+import org.fairdatatrain.trainhandler.api.dto.run.RunDTO;
+import org.springframework.context.ApplicationEvent;
+
+public class JobNotification extends ApplicationEvent {
+
+ private final RunDTO run;
+
+ private final JobDTO job;
+
+ JobNotification(Object source, RunDTO run, JobDTO job) {
+ super(source);
+ this.run = run;
+ this.job = job;
+ }
+
+ public RunDTO getRun() {
+ return run;
+ }
+
+ public JobDTO getJob() {
+ return job;
+ }
+}
diff --git a/src/main/java/org/fairdatatrain/trainhandler/service/async/JobNotificationListener.java b/src/main/java/org/fairdatatrain/trainhandler/service/async/JobNotificationListener.java
new file mode 100644
index 0000000..6a28e4a
--- /dev/null
+++ b/src/main/java/org/fairdatatrain/trainhandler/service/async/JobNotificationListener.java
@@ -0,0 +1,127 @@
+/**
+ * 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.trainhandler.service.async;
+
+import lombok.RequiredArgsConstructor;
+import lombok.Synchronized;
+import lombok.extern.slf4j.Slf4j;
+import org.fairdatatrain.trainhandler.api.dto.job.JobDTO;
+import org.fairdatatrain.trainhandler.config.DispatcherConfig;
+import org.springframework.context.event.EventListener;
+import org.springframework.stereotype.Component;
+import org.springframework.web.context.request.async.DeferredResult;
+
+import java.time.Instant;
+import java.util.*;
+
+import static java.lang.String.format;
+
+@Component
+@Slf4j
+@RequiredArgsConstructor
+public class JobNotificationListener {
+
+ private final DispatcherConfig config;
+
+ private final Map>> queues = new HashMap<>();
+
+ @EventListener
+ public void handleJobEventNotification(JobNotification notification) {
+ if (notification.getJob() != null) {
+ log.debug("Handling new job notification");
+ updateResults(notification.getJob());
+ }
+ }
+
+ @Synchronized
+ public void updateResults(JobDTO job) {
+ log.info(format(
+ "Updating results for job '%s' (version '%s'): start",
+ job.getUuid(), job.getVersion()
+ ));
+ final Instant now = Instant.now();
+ if (queues.containsKey(job.getUuid())) {
+ final List> remaining = new ArrayList<>();
+ queues.get(job.getUuid())
+ .stream()
+ .filter(container -> container.getTimeoutsAt().isAfter(now))
+ .forEach(container -> {
+ if (container.getVersion() <= job.getVersion()) {
+ log.info(format("Sending job for requested version: %s",
+ container.getVersion()));
+ container.getResult().setResult(job);
+ }
+ else {
+ remaining.add(container);
+ }
+ });
+ log.debug(format(
+ "Job %s queue size before %s and after %s",
+ job.getUuid(), queues.get(job.getUuid()).size(), remaining.size()
+ ));
+ queues.put(job.getUuid(), remaining);
+ }
+ log.info(format(
+ "Updating results for job '%s' (version '%s'): done",
+ job.getUuid(), job.getVersion()
+ ));
+ }
+
+ @Synchronized
+ public void enqueue(UUID jobUuid, Long version, DeferredResult result) {
+ log.info(format(
+ "Enqueueing deferred result for job '%s' (version '%s')",
+ jobUuid, version
+ ));
+ final Instant now = Instant.now();
+ if (!queues.containsKey(jobUuid)) {
+ log.debug("Initializing new job queue");
+ queues.put(jobUuid, new ArrayList<>());
+ }
+ else {
+ log.debug("Cleaning up existing job queue");
+ final List> remaining = new ArrayList<>();
+ queues.get(jobUuid).forEach(container -> {
+ if (container.getTimeoutsAt().isAfter(now)) {
+ remaining.add(container);
+ }
+ });
+ log.debug(format(
+ "Existing job queue size before %s and after %s",
+ queues.get(jobUuid).size(), remaining.size()
+ ));
+ queues.put(jobUuid, remaining);
+ }
+ queues.get(jobUuid).add(
+ new PollContainer<>(
+ version,
+ config.getPolling().getTimeoutForCurrentPoll(),
+ result
+ )
+ );
+ log.debug(format(
+ "Enqueueing deferred result for job '%s' (version '%s'): done",
+ jobUuid, version
+ ));
+ }
+}
diff --git a/src/main/java/org/fairdatatrain/trainhandler/service/async/PollContainer.java b/src/main/java/org/fairdatatrain/trainhandler/service/async/PollContainer.java
new file mode 100644
index 0000000..903c5ca
--- /dev/null
+++ b/src/main/java/org/fairdatatrain/trainhandler/service/async/PollContainer.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.trainhandler.service.async;
+
+import lombok.*;
+import org.springframework.web.context.request.async.DeferredResult;
+
+import java.time.Instant;
+
+@AllArgsConstructor
+@NoArgsConstructor
+@Getter
+@Setter
+@Builder
+public class PollContainer implements Comparable> {
+
+ private Long version;
+
+ private Instant timeoutsAt;
+
+ private DeferredResult result;
+
+ @Override
+ public int compareTo(PollContainer o) {
+ return version.compareTo(o.version);
+ }
+}
diff --git a/src/main/java/org/fairdatatrain/trainhandler/service/async/RunNotificationListener.java b/src/main/java/org/fairdatatrain/trainhandler/service/async/RunNotificationListener.java
new file mode 100644
index 0000000..427fbb5
--- /dev/null
+++ b/src/main/java/org/fairdatatrain/trainhandler/service/async/RunNotificationListener.java
@@ -0,0 +1,126 @@
+/**
+ * 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.trainhandler.service.async;
+
+import lombok.RequiredArgsConstructor;
+import lombok.Synchronized;
+import lombok.extern.slf4j.Slf4j;
+import org.fairdatatrain.trainhandler.api.dto.run.RunDTO;
+import org.fairdatatrain.trainhandler.config.DispatcherConfig;
+import org.springframework.context.event.EventListener;
+import org.springframework.stereotype.Component;
+import org.springframework.web.context.request.async.DeferredResult;
+
+import java.time.Instant;
+import java.util.*;
+
+import static java.lang.String.format;
+
+@Component
+@Slf4j
+@RequiredArgsConstructor
+public class RunNotificationListener {
+
+ private final DispatcherConfig config;
+
+ private final Map>> queues = new HashMap<>();
+
+ @EventListener
+ public void handleJobEventNotification(JobNotification notification) {
+ log.debug("Handling new run notification");
+ updateResults(notification.getRun());
+ }
+
+ @Synchronized
+ public void updateResults(RunDTO run) {
+ log.info(format(
+ "Updating results for run '%s' (version '%s')",
+ run.getUuid(), run.getVersion())
+ );
+ final Instant now = Instant.now();
+ if (queues.containsKey(run.getUuid())) {
+ final List> remaining = new ArrayList<>();
+ queues.get(run.getUuid())
+ .stream()
+ .filter(container -> container.getTimeoutsAt().isAfter(now))
+ .forEach(container -> {
+ if (container.getVersion() <= run.getVersion()) {
+ log.info(format("Sending run for requested version: %s",
+ container.getVersion()));
+ container.getResult().setResult(run);
+ }
+ else {
+ remaining.add(container);
+ }
+ });
+ log.debug(format(
+ "Run %s queue size before %s and after %s",
+ run.getUuid(), queues.get(run.getUuid()).size(), remaining.size()
+ ));
+ queues.put(run.getUuid(), remaining);
+ }
+ log.info(format(
+ "Updating results for run '%s' (version '%s'): done",
+ run.getUuid(), run.getVersion()
+ ));
+ }
+
+ @Synchronized
+ public void enqueue(UUID runUuid, Long version, DeferredResult result) {
+ log.info(format(
+ "Enqueueing deferred result for run '%s' (version '%s')",
+ runUuid, version
+ ));
+ final Instant now = Instant.now();
+ if (!queues.containsKey(runUuid)) {
+ log.debug("Initializing new run queue");
+ queues.put(runUuid, new ArrayList<>());
+ }
+ else {
+ log.debug("Cleaning up existing job queue");
+ final List> remaining = new ArrayList<>();
+ queues.get(runUuid).forEach(container -> {
+ if (container.getTimeoutsAt().isAfter(now)) {
+ remaining.add(container);
+ }
+ });
+ log.debug(format(
+ "Existing run queue size before %s and after %s",
+ queues.get(runUuid).size(), remaining.size()
+ ));
+ queues.put(runUuid, remaining);
+ }
+ queues.get(runUuid).add(
+ new PollContainer<>(
+ version,
+ config.getPolling().getTimeoutForCurrentPoll(),
+ result
+ )
+ );
+ log.debug(format(
+ "Enqueueing deferred result for run '%s' (version '%s'): done",
+ runUuid, version
+ ));
+ }
+
+}
diff --git a/src/main/java/org/fairdatatrain/trainhandler/service/dispatch/DispatchMapper.java b/src/main/java/org/fairdatatrain/trainhandler/service/dispatch/DispatchMapper.java
new file mode 100644
index 0000000..4dde076
--- /dev/null
+++ b/src/main/java/org/fairdatatrain/trainhandler/service/dispatch/DispatchMapper.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.trainhandler.service.dispatch;
+
+import org.fairdatatrain.trainhandler.api.controller.JobArtifactController;
+import org.fairdatatrain.trainhandler.api.controller.JobEventController;
+import org.fairdatatrain.trainhandler.data.model.Job;
+import org.springframework.beans.factory.annotation.Value;
+import org.springframework.stereotype.Component;
+
+@Component
+public class DispatchMapper {
+
+ @Value("${dispatcher.dispatch.root}")
+ private String dispatchRoot;
+
+ public DispatchPayload toPayload(Job job) {
+ return DispatchPayload
+ .builder()
+ .jobUuid(job.getUuid())
+ .secret(job.getSecret())
+ .trainUri(job.getRun().getPlan().getTrain().getUri())
+ .callbackEventLocation(makeCallback(
+ JobEventController.EVENT_CALLBACK_LOCATION, job
+ ))
+ .callbackArtifactLocation(makeCallback(
+ JobArtifactController.ARTIFACT_CALLBACK_LOCATION, job
+ ))
+ .build();
+ }
+
+ private String makeCallback(String fragment, Job job) {
+ return dispatchRoot + "/runs" + fragment
+ .replace("{runUuid}", job.getRun().getUuid().toString())
+ .replace("{jobUuid}", job.getUuid().toString());
+ }
+}
diff --git a/src/main/java/org/fairdatatrain/trainhandler/service/dispatch/DispatchPayload.java b/src/main/java/org/fairdatatrain/trainhandler/service/dispatch/DispatchPayload.java
new file mode 100644
index 0000000..43a2263
--- /dev/null
+++ b/src/main/java/org/fairdatatrain/trainhandler/service/dispatch/DispatchPayload.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.trainhandler.service.dispatch;
+
+import lombok.*;
+
+import javax.validation.constraints.NotNull;
+import java.util.UUID;
+
+@AllArgsConstructor
+@NoArgsConstructor
+@Getter
+@Setter
+@Builder(toBuilder = true)
+public class DispatchPayload {
+
+ @NotNull
+ private UUID jobUuid;
+
+ @NotNull
+ private String secret;
+
+ @NotNull
+ private String callbackEventLocation;
+
+ @NotNull
+ private String callbackArtifactLocation;
+
+ @NotNull
+ private String trainUri;
+}
diff --git a/src/main/java/org/fairdatatrain/trainhandler/service/dispatch/DispatchService.java b/src/main/java/org/fairdatatrain/trainhandler/service/dispatch/DispatchService.java
new file mode 100644
index 0000000..4cb146c
--- /dev/null
+++ b/src/main/java/org/fairdatatrain/trainhandler/service/dispatch/DispatchService.java
@@ -0,0 +1,77 @@
+/**
+ * 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.trainhandler.service.dispatch;
+
+import com.fasterxml.jackson.core.JsonProcessingException;
+import lombok.RequiredArgsConstructor;
+import lombok.extern.slf4j.Slf4j;
+import org.fairdatatrain.trainhandler.data.model.Job;
+import org.fairdatatrain.trainhandler.data.model.enums.JobStatus;
+import org.springframework.http.*;
+import org.springframework.stereotype.Service;
+import org.springframework.web.reactive.function.client.WebClient;
+import org.springframework.web.reactive.function.client.WebClientException;
+
+import static java.lang.String.format;
+
+@Slf4j
+@Service
+@RequiredArgsConstructor
+public class DispatchService {
+
+ private final DispatchMapper dispatchMapper;
+
+ private final WebClient webClient;
+
+ public void dispatch(Job job) throws JsonProcessingException {
+ if (!job.getStatus().equals(JobStatus.PREPARED)) {
+ throw new RuntimeException("Job not in state PREPARED, cannot dispatch");
+ }
+ final DispatchPayload payload = dispatchMapper.toPayload(job);
+ // TODO: should it POST directly to the station URI or some other endpoint?
+ // TODO: what should be the response?
+ final String uri = job.getTarget().getStation().getUri();
+ log.info(format("Dispatching job %s by POST to %s", job.getUuid(), uri));
+ try {
+ final String response = webClient
+ .post()
+ .uri(uri)
+ .contentType(MediaType.APPLICATION_JSON)
+ .bodyValue(payload)
+ .retrieve()
+ .bodyToMono(String.class)
+ .block();
+ }
+ catch (WebClientException exception) {
+ exception.printStackTrace();
+ log.warn(format(
+ "Dispatching job %s failed: %s", job.getUuid(), exception.getMessage()
+ ));
+ throw new RuntimeException(
+ "Station responded with status: " + exception.getMessage()
+ );
+ }
+ log.info(format("Dispatching job %s accepted", job.getUuid()));
+ }
+
+}
diff --git a/src/main/java/org/fairdatatrain/trainhandler/service/indexing/BaseIndexer.java b/src/main/java/org/fairdatatrain/trainhandler/service/indexing/BaseIndexer.java
new file mode 100644
index 0000000..3076703
--- /dev/null
+++ b/src/main/java/org/fairdatatrain/trainhandler/service/indexing/BaseIndexer.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.trainhandler.service.indexing;
+
+import lombok.RequiredArgsConstructor;
+import lombok.SneakyThrows;
+import lombok.extern.slf4j.Slf4j;
+import org.eclipse.rdf4j.model.Model;
+import org.eclipse.rdf4j.model.vocabulary.LDP;
+import org.eclipse.rdf4j.rio.RDFFormat;
+import org.springframework.http.HttpStatus;
+import org.springframework.http.MediaType;
+import org.springframework.stereotype.Service;
+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 java.util.List;
+
+import static java.lang.String.format;
+import static java.util.Optional.ofNullable;
+import static org.fairdatatrain.trainhandler.utils.RdfIOUtils.read;
+
+@Slf4j
+@Service
+@RequiredArgsConstructor
+public class BaseIndexer {
+
+ private final WebClient webClient;
+
+ public List extractChildren(Model model) {
+ return model
+ .filter(null, LDP.CONTAINS, null)
+ .parallelStream()
+ .map(statement -> statement.getObject().stringValue())
+ .toList();
+ }
+
+ @SneakyThrows
+ public Model makeRequest(String uri) {
+ log.info(format("Making request to '%s'", 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("Request to '%s' successfully received", uri));
+ final Model result = read(response, uri, RDFFormat.TURTLE);
+ log.info(format("Request to '%s' successfully parsed", uri));
+ return result;
+ }
+ catch (WebClientException exception) {
+ log.info(format("Request to '%s' failed", uri));
+ throw new HttpClientErrorException(
+ HttpStatus.INTERNAL_SERVER_ERROR,
+ ofNullable(exception.getMessage()).orElse("HTTP request failed")
+ );
+ }
+ }
+}
diff --git a/src/main/java/org/fairdatatrain/trainhandler/service/job/JobMapper.java b/src/main/java/org/fairdatatrain/trainhandler/service/job/JobMapper.java
new file mode 100644
index 0000000..a61b637
--- /dev/null
+++ b/src/main/java/org/fairdatatrain/trainhandler/service/job/JobMapper.java
@@ -0,0 +1,144 @@
+/**
+ * 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.trainhandler.service.job;
+
+import org.fairdatatrain.trainhandler.api.dto.job.JobDTO;
+import org.fairdatatrain.trainhandler.api.dto.job.JobSimpleDTO;
+import org.fairdatatrain.trainhandler.data.model.*;
+import org.fairdatatrain.trainhandler.data.model.enums.JobStatus;
+import org.fairdatatrain.trainhandler.service.job.artifact.JobArtifactMapper;
+import org.fairdatatrain.trainhandler.service.job.event.JobEventMapper;
+import org.fairdatatrain.trainhandler.service.run.RunMapper;
+import org.fairdatatrain.trainhandler.service.station.StationMapper;
+import org.springframework.context.annotation.Lazy;
+import org.springframework.stereotype.Component;
+
+import java.sql.Timestamp;
+import java.time.Instant;
+import java.util.Comparator;
+import java.util.Optional;
+import java.util.UUID;
+
+import static org.fairdatatrain.trainhandler.utils.RandomUtils.randomSecret;
+
+@Component
+public class JobMapper {
+
+ private final StationMapper stationMapper;
+
+ private final RunMapper runMapper;
+
+ private final JobEventMapper jobEventMapper;
+
+ private final JobArtifactMapper jobArtifactMapper;
+
+ public JobMapper(
+ StationMapper stationMapper,
+ JobEventMapper jobEventMapper,
+ JobArtifactMapper jobArtifactMapper,
+ @Lazy RunMapper runMapper
+ ) {
+ this.stationMapper = stationMapper;
+ this.runMapper = runMapper;
+ this.jobEventMapper = jobEventMapper;
+ this.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)
+ .map(Instant::toString)
+ .orElse(null))
+ .finishedAt(
+ Optional.ofNullable(job.getFinishedAt())
+ .map(Timestamp::toInstant)
+ .map(Instant::toString)
+ .orElse(null))
+ .target(stationMapper.toSimpleDTO(job.getTarget().getStation()))
+ .artifacts(job
+ .getArtifacts()
+ .stream()
+ .sorted(Comparator.comparing(JobArtifact::getOccurredAt))
+ .map(jobArtifactMapper::toDTO)
+ .toList()
+ )
+ .runUuid(job.getRun().getUuid())
+ .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)
+ .map(Instant::toString)
+ .orElse(null))
+ .finishedAt(
+ Optional.ofNullable(job.getFinishedAt())
+ .map(Timestamp::toInstant)
+ .map(Instant::toString)
+ .orElse(null))
+ .target(stationMapper.toSimpleDTO(job.getTarget().getStation()))
+ .run(runMapper.toSimpleDTO(job.getRun()))
+ .events(job
+ .getEvents()
+ .stream()
+ .sorted(Comparator.comparing(JobEvent::getOccurredAt))
+ .map(jobEventMapper::toDTO)
+ .toList()
+ )
+ .artifacts(job
+ .getArtifacts()
+ .stream()
+ .sorted(Comparator.comparing(JobArtifact::getOccurredAt))
+ .map(jobArtifactMapper::toDTO)
+ .toList()
+ )
+ .createdAt(job.getCreatedAt().toInstant().toString())
+ .updatedAt(job.getUpdatedAt().toInstant().toString())
+ .version(job.getVersion())
+ .build();
+ }
+
+ public Job fromTarget(Run run, PlanTarget target) {
+ return Job.builder()
+ .uuid(UUID.randomUUID())
+ .status(JobStatus.PREPARED)
+ .target(target)
+ .run(run)
+ .secret(randomSecret())
+ .createdAt(run.getCreatedAt())
+ .updatedAt(run.getUpdatedAt())
+ .version(run.getVersion())
+ .build();
+ }
+}
diff --git a/src/main/java/org/fairdatatrain/trainhandler/service/job/JobService.java b/src/main/java/org/fairdatatrain/trainhandler/service/job/JobService.java
new file mode 100644
index 0000000..080b47e
--- /dev/null
+++ b/src/main/java/org/fairdatatrain/trainhandler/service/job/JobService.java
@@ -0,0 +1,93 @@
+/**
+ * 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.trainhandler.service.job;
+
+import lombok.RequiredArgsConstructor;
+import lombok.extern.slf4j.Slf4j;
+import org.fairdatatrain.trainhandler.api.dto.job.JobDTO;
+import org.fairdatatrain.trainhandler.api.dto.job.JobSimpleDTO;
+import org.fairdatatrain.trainhandler.data.model.Job;
+import org.fairdatatrain.trainhandler.data.repository.JobRepository;
+import org.fairdatatrain.trainhandler.exception.NotFoundException;
+import org.fairdatatrain.trainhandler.service.async.JobNotificationListener;
+import org.springframework.data.domain.Page;
+import org.springframework.data.domain.Pageable;
+import org.springframework.stereotype.Service;
+import org.springframework.transaction.annotation.Transactional;
+import org.springframework.web.context.request.async.DeferredResult;
+
+import java.util.UUID;
+
+import static java.lang.String.format;
+
+@Service
+@RequiredArgsConstructor
+@Slf4j
+public class JobService {
+
+ public static final String ENTITY_NAME = "Job";
+
+ private final JobRepository jobRepository;
+
+ private final JobMapper jobMapper;
+
+ private final JobNotificationListener jobNotificationListener;
+
+ public Job getByIdOrThrow(UUID uuid) throws NotFoundException {
+ return jobRepository
+ .findById(uuid)
+ .orElseThrow(() -> new NotFoundException(ENTITY_NAME, uuid));
+ }
+
+ public Page getJobsForRun(UUID runUuid, Pageable pageable) {
+ return jobRepository
+ .findAllByRunUuid(runUuid, pageable)
+ .map(jobMapper::toSimpleDTO);
+ }
+
+ public JobDTO getSingle(UUID runUuid, UUID jobUuid) throws NotFoundException {
+ final Job job = getByIdOrThrow(jobUuid);
+ if (!job.getRun().getUuid().equals(runUuid)) {
+ throw new NotFoundException(ENTITY_NAME, jobUuid);
+ }
+ return jobMapper.toDTO(job);
+ }
+
+ @Transactional
+ public void poll(
+ UUID jobUuid,
+ DeferredResult result,
+ Long version,
+ JobDTO currentJob
+ ) {
+ log.info(format("REQUESTED VERSION: %s", version));
+ log.info(format("CURRENT VERSION: %s", currentJob.getVersion()));
+ if (version < currentJob.getVersion()) {
+ result.setResult(currentJob);
+ }
+ else {
+ log.info("No job update at this point, enqueueing...");
+ jobNotificationListener.enqueue(jobUuid, version, result);
+ }
+ }
+}
diff --git a/src/main/java/org/fairdatatrain/trainhandler/service/job/artifact/JobArtifactMapper.java b/src/main/java/org/fairdatatrain/trainhandler/service/job/artifact/JobArtifactMapper.java
new file mode 100644
index 0000000..d291a6b
--- /dev/null
+++ b/src/main/java/org/fairdatatrain/trainhandler/service/job/artifact/JobArtifactMapper.java
@@ -0,0 +1,72 @@
+/**
+ * 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.trainhandler.service.job.artifact;
+
+import org.fairdatatrain.trainhandler.api.dto.job.JobArtifactCreateDTO;
+import org.fairdatatrain.trainhandler.api.dto.job.JobArtifactDTO;
+import org.fairdatatrain.trainhandler.data.model.Job;
+import org.fairdatatrain.trainhandler.data.model.JobArtifact;
+import org.fairdatatrain.trainhandler.data.model.enums.ArtifactStorage;
+import org.springframework.stereotype.Component;
+
+import java.sql.Timestamp;
+import java.util.UUID;
+
+import static org.fairdatatrain.trainhandler.utils.TimeUtils.now;
+
+@Component
+public class JobArtifactMapper {
+ public JobArtifactDTO toDTO(JobArtifact artifact) {
+ return JobArtifactDTO
+ .builder()
+ .uuid(artifact.getUuid())
+ .displayName(artifact.getDisplayName())
+ .filename(artifact.getFilename())
+ .bytesize(artifact.getBytesize())
+ .contentType(artifact.getContentType())
+ .hash(artifact.getHash())
+ .occurredAt(artifact.getOccurredAt().toInstant().toString())
+ .createdAt(artifact.getCreatedAt().toInstant().toString())
+ .updatedAt(artifact.getUpdatedAt().toInstant().toString())
+ .build();
+ }
+
+ public JobArtifact fromCreateDTO(JobArtifactCreateDTO reqDto, Job job, byte[] data) {
+ final Timestamp now = now();
+ return JobArtifact
+ .builder()
+ .uuid(UUID.randomUUID())
+ .displayName(reqDto.getDisplayName())
+ .filename(reqDto.getFilename())
+ .hash(reqDto.getHash())
+ .bytesize(reqDto.getBytesize())
+ .contentType(reqDto.getContentType())
+ .data(data)
+ .storage(ArtifactStorage.POSTGRES)
+ .occurredAt(Timestamp.from(reqDto.getOccurredAt()))
+ .job(job)
+ .createdAt(now)
+ .updatedAt(now)
+ .build();
+ }
+}
diff --git a/src/main/java/org/fairdatatrain/trainhandler/service/job/artifact/JobArtifactService.java b/src/main/java/org/fairdatatrain/trainhandler/service/job/artifact/JobArtifactService.java
new file mode 100644
index 0000000..94fc96b
--- /dev/null
+++ b/src/main/java/org/fairdatatrain/trainhandler/service/job/artifact/JobArtifactService.java
@@ -0,0 +1,158 @@
+/**
+ * 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.trainhandler.service.job.artifact;
+
+import lombok.RequiredArgsConstructor;
+import lombok.extern.slf4j.Slf4j;
+import org.fairdatatrain.trainhandler.api.dto.job.JobArtifactCreateDTO;
+import org.fairdatatrain.trainhandler.api.dto.job.JobArtifactDTO;
+import org.fairdatatrain.trainhandler.data.model.Job;
+import org.fairdatatrain.trainhandler.data.model.JobArtifact;
+import org.fairdatatrain.trainhandler.data.model.enums.ArtifactStorage;
+import org.fairdatatrain.trainhandler.data.repository.JobArtifactRepository;
+import org.fairdatatrain.trainhandler.data.repository.JobRepository;
+import org.fairdatatrain.trainhandler.data.repository.RunRepository;
+import org.fairdatatrain.trainhandler.exception.JobSecurityException;
+import org.fairdatatrain.trainhandler.exception.NotFoundException;
+import org.fairdatatrain.trainhandler.service.job.JobService;
+import org.fairdatatrain.trainhandler.service.run.RunService;
+import org.springframework.stereotype.Service;
+import org.springframework.transaction.annotation.Transactional;
+
+import javax.persistence.EntityManager;
+import javax.persistence.PersistenceContext;
+import javax.validation.ValidationException;
+import java.security.MessageDigest;
+import java.security.NoSuchAlgorithmException;
+import java.util.Base64;
+import java.util.List;
+import java.util.Objects;
+import java.util.UUID;
+
+import static java.lang.String.format;
+import static org.fairdatatrain.trainhandler.utils.HashUtils.bytesToHex;
+
+@Service
+@RequiredArgsConstructor
+@Slf4j
+public class JobArtifactService {
+
+ public static final String ENTITY_NAME = "JobArtifact";
+
+ private final RunRepository runRepository;
+
+ private final JobRepository jobRepository;
+
+ private final JobArtifactRepository jobArtifactRepository;
+
+ private final JobArtifactMapper jobArtifactMapper;
+
+ private final JobService jobService;
+
+ @PersistenceContext
+ private final EntityManager entityManager;
+
+ public JobArtifact getByIdOrThrow(UUID uuid) throws NotFoundException {
+ return jobArtifactRepository
+ .findById(uuid)
+ .orElseThrow(() -> new NotFoundException(ENTITY_NAME, uuid));
+ }
+
+ public List getArtifacts(UUID runUuid, UUID jobUuid) throws NotFoundException {
+ final Job job = jobService.getByIdOrThrow(jobUuid);
+ if (!job.getRun().getUuid().equals(runUuid)) {
+ throw new NotFoundException(JobService.ENTITY_NAME, jobUuid);
+ }
+ return job.getArtifacts().stream().map(jobArtifactMapper::toDTO).toList();
+ }
+
+ public JobArtifact getArtifact(
+ UUID runUuid, UUID jobUuid, UUID artifactUuid
+ ) throws NotFoundException {
+ final JobArtifact artifact = getByIdOrThrow(artifactUuid);
+ if (!artifact.getJob().getUuid().equals(jobUuid)) {
+ throw new NotFoundException(JobService.ENTITY_NAME, jobUuid);
+ }
+ if (!artifact.getJob().getRun().getUuid().equals(runUuid)) {
+ throw new NotFoundException(RunService.ENTITY_NAME, runUuid);
+ }
+ return artifact;
+ }
+
+ 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 JobArtifactDTO createArtifact(
+ UUID runUuid, UUID jobUuid, JobArtifactCreateDTO reqDto
+ ) throws NotFoundException, JobSecurityException {
+ final Job job = jobService.getByIdOrThrow(jobUuid);
+ if (!job.getRun().getUuid().equals(runUuid)) {
+ throw new NotFoundException(JobService.ENTITY_NAME, jobUuid);
+ }
+ if (!Objects.equals(job.getSecret(), reqDto.getSecret())) {
+ throw new JobSecurityException("Incorrect secret for creating job event");
+ }
+ if (job.getRemoteId() == null) {
+ job.setRemoteId(reqDto.getRemoteId());
+ }
+ else if (!Objects.equals(job.getRemoteId(), reqDto.getRemoteId())) {
+ throw new JobSecurityException("Incorrect remote ID for creating job event");
+ }
+ final byte[] data = Base64.getDecoder().decode(reqDto.getBase64data());
+ validate(reqDto, data);
+ final JobArtifact jobArtifact = jobArtifactRepository.saveAndFlush(
+ jobArtifactMapper.fromCreateDTO(reqDto, job, data)
+ );
+ job.setVersion(jobArtifact.getUpdatedAt().toInstant().toEpochMilli());
+ jobRepository.saveAndFlush(job);
+ job.getRun().setVersion(job.getVersion());
+ runRepository.saveAndFlush(job.getRun());
+ entityManager.flush();
+ entityManager.refresh(jobArtifact);
+ return jobArtifactMapper.toDTO(jobArtifact);
+ }
+
+ private void validate(JobArtifactCreateDTO reqDto, byte[] data) {
+ final MessageDigest digest;
+ try {
+ digest = MessageDigest.getInstance("SHA-256");
+ }
+ catch (NoSuchAlgorithmException exc) {
+ throw new RuntimeException("SHA-256 hashing is not supported");
+ }
+ final String hash = bytesToHex(digest.digest(data));
+ if (data.length != reqDto.getBytesize()) {
+ throw new ValidationException("Bytesize does not match");
+ }
+ if (!hash.equals(reqDto.getHash())) {
+ throw new ValidationException("SHA-256 hash does not match");
+ }
+ }
+}
diff --git a/src/main/java/org/fairdatatrain/trainhandler/service/job/event/JobEventMapper.java b/src/main/java/org/fairdatatrain/trainhandler/service/job/event/JobEventMapper.java
new file mode 100644
index 0000000..9c4ff06
--- /dev/null
+++ b/src/main/java/org/fairdatatrain/trainhandler/service/job/event/JobEventMapper.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.trainhandler.service.job.event;
+
+import org.fairdatatrain.trainhandler.api.dto.job.JobEventCreateDTO;
+import org.fairdatatrain.trainhandler.api.dto.job.JobEventDTO;
+import org.fairdatatrain.trainhandler.data.model.Job;
+import org.fairdatatrain.trainhandler.data.model.JobEvent;
+import org.springframework.stereotype.Component;
+
+import java.sql.Timestamp;
+import java.util.UUID;
+
+import static org.fairdatatrain.trainhandler.utils.TimeUtils.now;
+
+@Component
+public class JobEventMapper {
+ public JobEventDTO toDTO(JobEvent jobEvent) {
+ return JobEventDTO
+ .builder()
+ .uuid(jobEvent.getUuid())
+ .message(jobEvent.getMessage())
+ .resultStatus(jobEvent.getResultStatus())
+ .createdAt(jobEvent.getCreatedAt().toInstant().toString())
+ .updatedAt(jobEvent.getUpdatedAt().toInstant().toString())
+ .occurredAt(jobEvent.getOccurredAt().toInstant().toString())
+ .build();
+ }
+
+ public JobEvent fromCreateDTO(JobEventCreateDTO reqDto, Job job) {
+ final Timestamp now = now();
+ return JobEvent
+ .builder()
+ .uuid(UUID.randomUUID())
+ .message(reqDto.getMessage())
+ .resultStatus(reqDto.getResultStatus())
+ .occurredAt(Timestamp.from(reqDto.getOccurredAt()))
+ .job(job)
+ .createdAt(now)
+ .updatedAt(now)
+ .build();
+ }
+}
diff --git a/src/main/java/org/fairdatatrain/trainhandler/service/job/event/JobEventService.java b/src/main/java/org/fairdatatrain/trainhandler/service/job/event/JobEventService.java
new file mode 100644
index 0000000..4555ce5
--- /dev/null
+++ b/src/main/java/org/fairdatatrain/trainhandler/service/job/event/JobEventService.java
@@ -0,0 +1,171 @@
+/**
+ * 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.trainhandler.service.job.event;
+
+import lombok.RequiredArgsConstructor;
+import lombok.extern.slf4j.Slf4j;
+import org.fairdatatrain.trainhandler.api.dto.job.JobDTO;
+import org.fairdatatrain.trainhandler.api.dto.job.JobEventCreateDTO;
+import org.fairdatatrain.trainhandler.api.dto.job.JobEventDTO;
+import org.fairdatatrain.trainhandler.api.dto.run.RunDTO;
+import org.fairdatatrain.trainhandler.data.model.Job;
+import org.fairdatatrain.trainhandler.data.model.JobEvent;
+import org.fairdatatrain.trainhandler.data.model.Run;
+import org.fairdatatrain.trainhandler.data.model.enums.JobStatus;
+import org.fairdatatrain.trainhandler.data.model.enums.RunStatus;
+import org.fairdatatrain.trainhandler.data.repository.JobEventRepository;
+import org.fairdatatrain.trainhandler.data.repository.JobRepository;
+import org.fairdatatrain.trainhandler.data.repository.RunRepository;
+import org.fairdatatrain.trainhandler.exception.JobSecurityException;
+import org.fairdatatrain.trainhandler.exception.NotFoundException;
+import org.fairdatatrain.trainhandler.service.async.AsyncEventPublisher;
+import org.fairdatatrain.trainhandler.service.job.JobMapper;
+import org.fairdatatrain.trainhandler.service.job.JobService;
+import org.fairdatatrain.trainhandler.service.run.RunMapper;
+import org.springframework.stereotype.Service;
+import org.springframework.transaction.annotation.Transactional;
+
+import javax.persistence.EntityManager;
+import javax.persistence.PersistenceContext;
+import java.sql.Timestamp;
+import java.util.*;
+
+@Service
+@RequiredArgsConstructor
+@Slf4j
+public class JobEventService {
+
+ public static final String ENTITY_NAME = "JobEvent";
+
+ private final RunRepository runRepository;
+
+ private final JobRepository jobRepository;
+
+ private final JobMapper jobMapper;
+
+ private final RunMapper runMapper;
+
+ private final JobService jobService;
+
+ private final JobEventRepository jobEventRepository;
+
+ private final JobEventMapper jobEventMapper;
+
+ private final AsyncEventPublisher asyncEventPublisher;
+
+ @PersistenceContext
+ private final EntityManager entityManager;
+
+ public JobEvent getByIdOrThrow(UUID uuid) throws NotFoundException {
+ return jobEventRepository
+ .findById(uuid)
+ .orElseThrow(() -> new NotFoundException(ENTITY_NAME, uuid));
+ }
+
+ public List getEvents(UUID runUuid, UUID jobUuid) throws NotFoundException {
+ final Job job = jobService.getByIdOrThrow(jobUuid);
+ if (!job.getRun().getUuid().equals(runUuid)) {
+ throw new NotFoundException(JobService.ENTITY_NAME, jobUuid);
+ }
+ return jobEventRepository
+ .findAllByJobOrderByOccurredAtAsc(job)
+ .parallelStream()
+ .map(jobEventMapper::toDTO)
+ .toList();
+ }
+
+ @Transactional
+ public JobEventDTO createEvent(
+ UUID runUuid, UUID jobUuid, JobEventCreateDTO reqDto
+ ) throws NotFoundException, JobSecurityException {
+ final Job job = jobService.getByIdOrThrow(jobUuid);
+ final Run run = job.getRun();
+ if (!run.getUuid().equals(runUuid)) {
+ throw new NotFoundException(JobService.ENTITY_NAME, jobUuid);
+ }
+ if (!Objects.equals(job.getSecret(), reqDto.getSecret())) {
+ throw new JobSecurityException("Incorrect secret for creating job event");
+ }
+ if (job.getRemoteId() == null) {
+ job.setRemoteId(reqDto.getRemoteId());
+ }
+ else if (!Objects.equals(job.getRemoteId(), reqDto.getRemoteId())) {
+ throw new JobSecurityException("Incorrect remote ID for creating job event");
+ }
+ if (reqDto.getResultStatus() != null) {
+ job.setStatus(reqDto.getResultStatus());
+ if (job.getFinishedAt() == null && job.isFinished()) {
+ job.setFinishedAt(Timestamp.from(reqDto.getOccurredAt()));
+ }
+ run.setStatus(getNextRunStatus(job, reqDto));
+ if (run.getFinishedAt() == null && run.isFinished()) {
+ run.setFinishedAt(Timestamp.from(reqDto.getOccurredAt()));
+ }
+ }
+ final JobEvent jobEvent = jobEventRepository.saveAndFlush(
+ jobEventMapper.fromCreateDTO(reqDto, job)
+ );
+ job.setVersion(jobEvent.getUpdatedAt().toInstant().toEpochMilli());
+ jobRepository.saveAndFlush(job);
+ job.getRun().setVersion(job.getVersion());
+ runRepository.saveAndFlush(run);
+ entityManager.flush();
+ entityManager.refresh(jobEvent);
+ return jobEventMapper.toDTO(jobEvent);
+ }
+
+ @Transactional
+ public void notify(UUID jobUuid) throws NotFoundException {
+ final Job job = jobService.getByIdOrThrow(jobUuid);
+ final JobDTO jobDto = jobMapper.toDTO(job);
+ final RunDTO runDto = runMapper.toDTO(job.getRun());
+ asyncEventPublisher.publishNewJobEventNotification(runDto, jobDto);
+ }
+
+ private RunStatus getNextRunStatus(Job job, JobEventCreateDTO reqDto) {
+ final List jobStatuses = job.getRun().getJobs().stream().map(job1 -> {
+ if (job1.getUuid() == job.getUuid()) {
+ return reqDto.getResultStatus();
+ }
+ else {
+ return job.getStatus();
+ }
+ }).toList();
+ if (jobStatuses.stream().anyMatch(JobStatus.RUNNING::equals)) {
+ return RunStatus.RUNNING;
+ }
+ if (jobStatuses.stream().anyMatch(JobStatus.ABORTING::equals)) {
+ return RunStatus.ABORTING;
+ }
+ if (jobStatuses.stream().allMatch(JobStatus.ERRORED::equals)) {
+ return RunStatus.ERRORED;
+ }
+ if (jobStatuses.stream().allMatch(JobStatus.FINISHED::equals)) {
+ return RunStatus.FINISHED;
+ }
+ if (jobStatuses.stream().anyMatch(JobStatus.FAILED::equals)) {
+ return RunStatus.FAILED;
+ }
+ return job.getRun().getStatus();
+ }
+}
diff --git a/src/main/java/org/fairdatatrain/trainhandler/service/plan/PlanMapper.java b/src/main/java/org/fairdatatrain/trainhandler/service/plan/PlanMapper.java
new file mode 100644
index 0000000..d569c0d
--- /dev/null
+++ b/src/main/java/org/fairdatatrain/trainhandler/service/plan/PlanMapper.java
@@ -0,0 +1,118 @@
+/**
+ * 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.trainhandler.service.plan;
+
+import lombok.RequiredArgsConstructor;
+import org.fairdatatrain.trainhandler.api.dto.plan.*;
+import org.fairdatatrain.trainhandler.data.model.Plan;
+import org.fairdatatrain.trainhandler.data.model.PlanTarget;
+import org.fairdatatrain.trainhandler.data.model.Train;
+import org.fairdatatrain.trainhandler.service.station.StationMapper;
+import org.fairdatatrain.trainhandler.service.train.TrainMapper;
+import org.springframework.stereotype.Component;
+
+import java.sql.Timestamp;
+import java.util.Collections;
+import java.util.Comparator;
+import java.util.UUID;
+
+import static org.fairdatatrain.trainhandler.utils.TimeUtils.now;
+
+@Component
+@RequiredArgsConstructor
+public class PlanMapper {
+
+ private static final Comparator TARGET_CMP =
+ Comparator.comparing((PlanTargetDTO item) -> item.getStation().getTitle());
+
+ private final TrainMapper trainMapper;
+
+ private final StationMapper stationMapper;
+
+ public PlanDTO toDTO(Plan plan) {
+ return PlanDTO.builder()
+ .uuid(plan.getUuid())
+ .displayName(plan.getDisplayName())
+ .note(plan.getNote())
+ .train(trainMapper.toDTO(plan.getTrain()))
+ .targets(
+ plan.getTargets()
+ .stream()
+ .map(this::toPlanTargetDTO)
+ .sorted(TARGET_CMP)
+ .toList()
+ )
+ .publishArtifacts(plan.getPublishArtifacts())
+ .createdAt(plan.getCreatedAt().toInstant().toString())
+ .updatedAt(plan.getUpdatedAt().toInstant().toString())
+ .build();
+ }
+
+ public PlanSimpleDTO toSimpleDTO(Plan plan) {
+ return PlanSimpleDTO.builder()
+ .uuid(plan.getUuid())
+ .displayName(plan.getDisplayName())
+ .note(plan.getNote())
+ .train(trainMapper.toSimpleDTO(plan.getTrain()))
+ .targets(
+ plan.getTargets()
+ .stream()
+ .map(this::toPlanTargetDTO)
+ .sorted(TARGET_CMP)
+ .toList()
+ )
+ .publishArtifacts(plan.getPublishArtifacts())
+ .build();
+ }
+
+ public Plan fromCreateDTO(PlanCreateDTO reqDto, Train train) {
+ final Timestamp now = now();
+ return Plan.builder()
+ .uuid(UUID.randomUUID())
+ .displayName(reqDto.getDisplayName())
+ .note(reqDto.getNote())
+ .train(train)
+ .targets(Collections.emptyList())
+ .publishArtifacts(reqDto.getPublishArtifacts())
+ .createdAt(now)
+ .updatedAt(now)
+ .build();
+ }
+
+ public Plan fromUpdateDTO(PlanUpdateDTO reqDto, Plan plan, Train train) {
+ final Timestamp now = now();
+ plan.setTrain(train);
+ plan.setDisplayName(reqDto.getDisplayName());
+ plan.setNote(reqDto.getNote());
+ plan.setUpdatedAt(now);
+ plan.setPublishArtifacts(reqDto.getPublishArtifacts());
+ return plan;
+ }
+
+ public PlanTargetDTO toPlanTargetDTO(PlanTarget target) {
+ return PlanTargetDTO.builder()
+ .station(stationMapper.toSimpleDTO(target.getStation()))
+ .publishArtifacts(target.getPublishArtifacts())
+ .build();
+ }
+}
diff --git a/src/main/java/org/fairdatatrain/trainhandler/service/plan/PlanService.java b/src/main/java/org/fairdatatrain/trainhandler/service/plan/PlanService.java
new file mode 100644
index 0000000..c8f9edd
--- /dev/null
+++ b/src/main/java/org/fairdatatrain/trainhandler/service/plan/PlanService.java
@@ -0,0 +1,198 @@
+/**
+ * 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.trainhandler.service.plan;
+
+import lombok.RequiredArgsConstructor;
+import org.fairdatatrain.trainhandler.api.dto.plan.*;
+import org.fairdatatrain.trainhandler.data.model.Plan;
+import org.fairdatatrain.trainhandler.data.model.PlanTarget;
+import org.fairdatatrain.trainhandler.data.model.Station;
+import org.fairdatatrain.trainhandler.data.model.Train;
+import org.fairdatatrain.trainhandler.data.repository.PlanRepository;
+import org.fairdatatrain.trainhandler.data.repository.PlanTargetRepository;
+import org.fairdatatrain.trainhandler.exception.CannotPerformException;
+import org.fairdatatrain.trainhandler.exception.NotFoundException;
+import org.fairdatatrain.trainhandler.service.station.StationService;
+import org.fairdatatrain.trainhandler.service.train.TrainService;
+import org.springframework.data.domain.Page;
+import org.springframework.data.domain.Pageable;
+import org.springframework.stereotype.Service;
+import org.springframework.transaction.annotation.Transactional;
+
+import javax.persistence.EntityManager;
+import javax.persistence.PersistenceContext;
+import java.util.*;
+import java.util.function.Function;
+import java.util.stream.Collectors;
+
+import static java.lang.String.format;
+import static org.fairdatatrain.trainhandler.utils.CompareUtils.compareListContents;
+
+@Service
+@RequiredArgsConstructor
+public class PlanService {
+
+ private static final String ENTITY_NAME = "Plan";
+
+ private final PlanRepository planRepository;
+
+ private final PlanTargetRepository planTargetRepository;
+
+ private final PlanMapper planMapper;
+
+ private final PlanTargetMapper planTargetMapper;
+
+ private final TrainService trainService;
+
+ private final StationService stationService;
+
+ @PersistenceContext
+ private final EntityManager entityManager;
+
+ public Plan getByIdOrThrow(UUID uuid) throws NotFoundException {
+ return planRepository
+ .findById(uuid)
+ .orElseThrow(() -> new NotFoundException(ENTITY_NAME, uuid));
+ }
+
+ public Page getPaged(String query, Pageable pageable) {
+ return planRepository
+ .findByDisplayNameContainingIgnoreCase(query, pageable)
+ .map(planMapper::toSimpleDTO);
+ }
+
+ public PlanDTO getSingle(UUID uuid) throws NotFoundException {
+ final Plan plan = getByIdOrThrow(uuid);
+ return planMapper.toDTO(plan);
+ }
+
+ @Transactional
+ public PlanDTO create(PlanCreateDTO reqDto) throws NotFoundException {
+ final Train train = trainService.getByIdOrThrow(reqDto.getTrainUuid());
+ final Map stations = new HashMap<>();
+ for (PlanTargetChangeDTO targetDto : reqDto.getTargets()) {
+ stations.put(targetDto.getStationUuid(),
+ stationService.getByIdOrThrow(targetDto.getStationUuid()));
+ }
+ final Plan newPlan =
+ planRepository.saveAndFlush(planMapper.fromCreateDTO(reqDto, train));
+ entityManager.flush();
+ entityManager.refresh(newPlan);
+ final List targets =
+ reqDto.getTargets()
+ .stream()
+ .map(targetDto -> {
+ return planTargetMapper.forPlan(
+ newPlan,
+ targetDto,
+ stations.get(targetDto.getStationUuid())
+ );
+ })
+ .toList();
+ newPlan.setTargets(targets);
+ planTargetRepository.saveAllAndFlush(targets);
+ entityManager.flush();
+ entityManager.refresh(newPlan);
+ return planMapper.toDTO(newPlan);
+ }
+
+ @Transactional
+ public PlanDTO update(UUID uuid, PlanUpdateDTO reqDto)
+ throws NotFoundException, CannotPerformException {
+ final String action = "update";
+ final Plan plan = getByIdOrThrow(uuid);
+ final Set oldStationUuids = plan
+ .getTargets()
+ .stream()
+ .map(PlanTarget::getStation)
+ .map(Station::getUuid)
+ .collect(Collectors.toSet());
+ final Map oldTargets = plan
+ .getTargets()
+ .stream()
+ .collect(Collectors.toMap(PlanTarget::getStationUuid, Function.identity()));
+ final Set newStationUuids = reqDto
+ .getTargets()
+ .stream()
+ .map(PlanTargetChangeDTO::getStationUuid)
+ .collect(Collectors.toSet());
+ final boolean planExecuted = !plan.getRuns().isEmpty();
+ final boolean changeTrain = reqDto.getTrainUuid() != plan.getTrain().getUuid();
+ final boolean changeTargets =
+ compareListContents(newStationUuids, oldStationUuids);
+ if (planExecuted && changeTrain) {
+ throw new CannotPerformException(
+ ENTITY_NAME,
+ uuid,
+ action,
+ "It is not possible to change train of already used plan.");
+ }
+ if (planExecuted && changeTargets) {
+ throw new CannotPerformException(
+ ENTITY_NAME,
+ uuid,
+ action,
+ "It is not possible to change targets of already used plan.");
+ }
+ final Train train = trainService.getByIdOrThrow(reqDto.getTrainUuid());
+ final List targets = new ArrayList<>();
+ for (PlanTargetChangeDTO targetDto : reqDto.getTargets()) {
+ if (oldTargets.containsKey(targetDto.getStationUuid())) {
+ final PlanTarget target = oldTargets.get(targetDto.getStationUuid());
+ target.setPublishArtifacts(targetDto.getPublishArtifacts());
+ targets.add(planTargetRepository.saveAndFlush(target));
+ }
+ else {
+ final Station station =
+ stationService.getByIdOrThrow(targetDto.getStationUuid());
+ targets.add(planTargetRepository.saveAndFlush(
+ planTargetMapper.forPlan(plan, targetDto, station)
+ ));
+ }
+ }
+ for (PlanTarget target : plan.getTargets()) {
+ if (!newStationUuids.contains(target.getStation().getUuid())) {
+ planTargetRepository.delete(target);
+ }
+ }
+ plan.setTargets(targets);
+ final Plan updatedPlan =
+ planRepository.saveAndFlush(planMapper.fromUpdateDTO(reqDto, plan, train));
+ entityManager.flush();
+ entityManager.refresh(updatedPlan);
+ return planMapper.toDTO(updatedPlan);
+ }
+
+ @Transactional
+ public void delete(UUID uuid) throws NotFoundException, CannotPerformException {
+ final Plan plan = getByIdOrThrow(uuid);
+ if (!plan.getRuns().isEmpty()) {
+ throw new CannotPerformException(
+ ENTITY_NAME,
+ uuid,
+ "delete",
+ format("There are already %d runs for this plan.", plan.getRuns().size()));
+ }
+ planRepository.delete(plan);
+ }
+}
diff --git a/src/main/java/org/fairdatatrain/trainhandler/service/plan/PlanTargetMapper.java b/src/main/java/org/fairdatatrain/trainhandler/service/plan/PlanTargetMapper.java
new file mode 100644
index 0000000..f15e32a
--- /dev/null
+++ b/src/main/java/org/fairdatatrain/trainhandler/service/plan/PlanTargetMapper.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.trainhandler.service.plan;
+
+import lombok.RequiredArgsConstructor;
+import org.fairdatatrain.trainhandler.api.dto.plan.PlanTargetChangeDTO;
+import org.fairdatatrain.trainhandler.data.model.Plan;
+import org.fairdatatrain.trainhandler.data.model.PlanTarget;
+import org.fairdatatrain.trainhandler.data.model.Station;
+import org.springframework.stereotype.Component;
+
+import java.util.UUID;
+
+@Component
+@RequiredArgsConstructor
+public class PlanTargetMapper {
+
+ public PlanTarget forPlan(Plan newPlan, PlanTargetChangeDTO targetDto, Station station) {
+ return PlanTarget.builder()
+ .uuid(UUID.randomUUID())
+ .plan(newPlan)
+ .station(station)
+ .publishArtifacts(targetDto.getPublishArtifacts())
+ .createdAt(newPlan.getCreatedAt())
+ .updatedAt(newPlan.getUpdatedAt())
+ .build();
+ }
+}
diff --git a/src/main/java/org/fairdatatrain/trainhandler/service/plan/latest/PlanLatestService.java b/src/main/java/org/fairdatatrain/trainhandler/service/plan/latest/PlanLatestService.java
new file mode 100644
index 0000000..fd428b7
--- /dev/null
+++ b/src/main/java/org/fairdatatrain/trainhandler/service/plan/latest/PlanLatestService.java
@@ -0,0 +1,91 @@
+/**
+ * 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.trainhandler.service.plan.latest;
+
+import lombok.RequiredArgsConstructor;
+import lombok.extern.slf4j.Slf4j;
+import org.fairdatatrain.trainhandler.api.dto.job.JobArtifactDTO;
+import org.fairdatatrain.trainhandler.data.model.JobArtifact;
+import org.fairdatatrain.trainhandler.data.model.Plan;
+import org.fairdatatrain.trainhandler.data.repository.JobArtifactRepository;
+import org.fairdatatrain.trainhandler.exception.NotFoundException;
+import org.fairdatatrain.trainhandler.service.job.artifact.JobArtifactMapper;
+import org.fairdatatrain.trainhandler.service.job.artifact.JobArtifactService;
+import org.fairdatatrain.trainhandler.service.plan.PlanService;
+import org.springframework.stereotype.Service;
+
+import java.util.List;
+import java.util.UUID;
+
+@Slf4j
+@Service
+@RequiredArgsConstructor
+public class PlanLatestService {
+
+ private final PlanService planService;
+
+ private final JobArtifactService jobArtifactService;
+
+ private final JobArtifactRepository jobArtifactRepository;
+
+ private final JobArtifactMapper jobArtifactMapper;
+
+ public JobArtifact getLatestJobArtifact(UUID planUuid, UUID artifactUuid) throws NotFoundException {
+ final JobArtifact jobArtifact = jobArtifactService.getByIdOrThrow(artifactUuid);
+ if (!jobArtifact.getJob().getTarget().getPublishArtifacts()
+ || !jobArtifact.getJob().getTarget().getPlan().getUuid().equals(planUuid)) {
+ throw new NotFoundException("Cannot find public artifact", artifactUuid);
+ }
+ return jobArtifact;
+ }
+
+ public List getLatestArtifacts(UUID planUuid) throws NotFoundException {
+ final Plan plan = planService.getByIdOrThrow(planUuid);
+ if (!plan.getPublishArtifacts()) {
+ return List.of();
+ }
+ return jobArtifactRepository
+ .getPublicJobArtifactsOfPlan(planUuid)
+ .stream()
+ .map(jobArtifactMapper::toDTO)
+ .toList();
+ }
+
+ public List getLatestArtifactsOfTarget(
+ UUID planUuid, UUID targetUuid
+ ) throws NotFoundException {
+ final Plan plan = planService.getByIdOrThrow(planUuid);
+ if (!plan.getPublishArtifacts()) {
+ return List.of();
+ }
+ return jobArtifactRepository
+ .getPublicJobArtifactsOfTarget(targetUuid)
+ .stream()
+ .map(jobArtifactMapper::toDTO)
+ .toList();
+ }
+
+ public byte[] getArtifactData(JobArtifact jobArtifact) {
+ return jobArtifactService.getArtifactData(jobArtifact);
+ }
+}
diff --git a/src/main/java/org/fairdatatrain/trainhandler/service/run/RunDispatcher.java b/src/main/java/org/fairdatatrain/trainhandler/service/run/RunDispatcher.java
new file mode 100644
index 0000000..03d84e3
--- /dev/null
+++ b/src/main/java/org/fairdatatrain/trainhandler/service/run/RunDispatcher.java
@@ -0,0 +1,142 @@
+/**
+ * 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.trainhandler.service.run;
+
+import lombok.RequiredArgsConstructor;
+import lombok.SneakyThrows;
+import lombok.extern.slf4j.Slf4j;
+import org.fairdatatrain.trainhandler.api.dto.job.JobEventCreateDTO;
+import org.fairdatatrain.trainhandler.data.model.Job;
+import org.fairdatatrain.trainhandler.data.model.Run;
+import org.fairdatatrain.trainhandler.data.model.enums.JobStatus;
+import org.fairdatatrain.trainhandler.data.model.enums.RunStatus;
+import org.fairdatatrain.trainhandler.data.repository.JobRepository;
+import org.fairdatatrain.trainhandler.data.repository.RunRepository;
+import org.fairdatatrain.trainhandler.service.dispatch.DispatchService;
+import org.fairdatatrain.trainhandler.service.job.event.JobEventService;
+import org.springframework.data.domain.Page;
+import org.springframework.data.domain.Pageable;
+import org.springframework.scheduling.annotation.Scheduled;
+import org.springframework.stereotype.Service;
+import org.springframework.transaction.annotation.Transactional;
+
+import java.time.Instant;
+import java.util.HashSet;
+import java.util.Set;
+import java.util.UUID;
+
+import static java.lang.String.format;
+import static org.fairdatatrain.trainhandler.utils.TimeUtils.now;
+
+@Slf4j
+@Service
+@RequiredArgsConstructor
+public class RunDispatcher {
+
+ private final DispatchService dispatchService;
+
+ private final JobRepository jobRepository;
+
+ private final RunRepository runRepository;
+
+ private final JobEventService jobEventService;
+
+ @Scheduled(
+ initialDelayString = "${dispatcher.dispatch.initDelay:PT1M}",
+ fixedRateString = "${dispatcher.dispatch.interval:PT1M}"
+ )
+ public void dispatchScheduledRuns() {
+ log.debug("Dispatching scheduled runs");
+ final Set dispatchedRuns = new HashSet<>();
+ boolean dispatching = true;
+ while (dispatching) {
+ final UUID dispatchedRun = tryDispatchRun();
+ dispatching = dispatchedRun != null && !dispatchedRuns.contains(dispatchedRun);
+ if (dispatching) {
+ dispatchedRuns.add(dispatchedRun);
+ log.debug("Dispatched run: " + dispatchedRun);
+ }
+ else {
+ log.debug("No more runs to be dispatched now");
+ }
+ }
+ }
+
+ public UUID tryDispatchRun() {
+ final Page runs = runRepository.findRunToDispatch(
+ now(),
+ Pageable.ofSize(1).withPage(0)
+ );
+ if (runs.isEmpty()) {
+ return null;
+ }
+ final Run run = runs.getContent().get(0);
+ log.info("Run selected for dispatching: " + run.getUuid());
+ dispatchRun(run);
+ return run.getUuid();
+ }
+
+ @Transactional
+ protected void dispatchRun(Run run) {
+ run.setStartedAt(now());
+ run.getJobs().forEach(job -> {
+ job.setStartedAt(now());
+ jobRepository.save(job);
+ });
+ run.setStatus(RunStatus.RUNNING);
+ runRepository.save(run);
+ run.getJobs().forEach(job -> dispatchJob(run.getUuid(), job));
+ }
+
+ @SneakyThrows
+ protected void dispatchJob(UUID runUuid, Job job) {
+ try {
+ jobEventService.createEvent(
+ runUuid,
+ job.getUuid(),
+ JobEventCreateDTO.builder()
+ .message("Dispatching job from train handler")
+ .occurredAt(Instant.now())
+ .resultStatus(JobStatus.RUNNING)
+ .secret(job.getSecret())
+ .build()
+ );
+ jobEventService.notify(job.getUuid());
+ dispatchService.dispatch(job);
+ }
+ catch (Exception ex) {
+ log.warn(format("Failed to dispatch job %s: %s", job.getUuid(), ex.getMessage()));
+ jobEventService.createEvent(
+ runUuid,
+ job.getUuid(),
+ JobEventCreateDTO.builder()
+ .message(format("Dispatch failed: %s", ex.getMessage()))
+ .occurredAt(Instant.now())
+ .resultStatus(JobStatus.ERRORED)
+ .secret(job.getSecret())
+ .build()
+ );
+ jobEventService.notify(job.getUuid());
+ }
+ }
+}
diff --git a/src/main/java/org/fairdatatrain/trainhandler/service/run/RunMapper.java b/src/main/java/org/fairdatatrain/trainhandler/service/run/RunMapper.java
new file mode 100644
index 0000000..e958578
--- /dev/null
+++ b/src/main/java/org/fairdatatrain/trainhandler/service/run/RunMapper.java
@@ -0,0 +1,153 @@
+/**
+ * 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.trainhandler.service.run;
+
+import lombok.RequiredArgsConstructor;
+import org.fairdatatrain.trainhandler.api.dto.job.JobSimpleDTO;
+import org.fairdatatrain.trainhandler.api.dto.run.RunCreateDTO;
+import org.fairdatatrain.trainhandler.api.dto.run.RunDTO;
+import org.fairdatatrain.trainhandler.api.dto.run.RunSimpleDTO;
+import org.fairdatatrain.trainhandler.api.dto.run.RunUpdateDTO;
+import org.fairdatatrain.trainhandler.data.model.Plan;
+import org.fairdatatrain.trainhandler.data.model.Run;
+import org.fairdatatrain.trainhandler.data.model.enums.RunStatus;
+import org.fairdatatrain.trainhandler.service.job.JobMapper;
+import org.fairdatatrain.trainhandler.service.plan.PlanMapper;
+import org.springframework.stereotype.Component;
+
+import java.sql.Timestamp;
+import java.time.Instant;
+import java.util.Comparator;
+import java.util.List;
+import java.util.Optional;
+import java.util.UUID;
+
+import static org.fairdatatrain.trainhandler.utils.TimeUtils.now;
+
+@Component
+@RequiredArgsConstructor
+public class RunMapper {
+
+ private static final Comparator JOB_SIMPLE_CMP =
+ Comparator.comparing((JobSimpleDTO item) -> item.getTarget().getTitle());
+
+ private final PlanMapper planMapper;
+
+ private final JobMapper jobMapper;
+
+ public RunSimpleDTO toSimpleDTO(Run run) {
+ return RunSimpleDTO.builder()
+ .uuid(run.getUuid())
+ .displayName(run.getDisplayName())
+ .note(run.getNote())
+ .status(run.getStatus())
+ .trainUuid(run.getPlan().getTrain().getUuid())
+ .planUuid(run.getPlan().getUuid())
+ .shouldStartAt(
+ Optional.ofNullable(run.getShouldStartAt())
+ .map(Timestamp::toInstant)
+ .map(Instant::toString)
+ .orElse(null))
+ .startedAt(
+ Optional.ofNullable(run.getStartedAt())
+ .map(Timestamp::toInstant)
+ .map(Instant::toString)
+ .orElse(null))
+ .finishedAt(
+ Optional.ofNullable(run.getFinishedAt())
+ .map(Timestamp::toInstant)
+ .map(Instant::toString)
+ .orElse(null))
+ .createdAt(run.getCreatedAt().toInstant().toString())
+ .updatedAt(run.getUpdatedAt().toInstant().toString())
+ .build();
+ }
+
+ public RunDTO toDTO(Run run) {
+ final List jobs = run
+ .getJobs()
+ .stream()
+ .map(jobMapper::toSimpleDTO)
+ .sorted(JOB_SIMPLE_CMP)
+ .toList();
+ return RunDTO.builder()
+ .uuid(run.getUuid())
+ .displayName(run.getDisplayName())
+ .note(run.getNote())
+ .status(run.getStatus())
+ .plan(planMapper.toSimpleDTO(run.getPlan()))
+ .jobs(jobs)
+ .version(run.getVersion())
+ .shouldStartAt(
+ Optional.ofNullable(run.getShouldStartAt())
+ .map(Timestamp::toInstant)
+ .map(Instant::toString)
+ .orElse(null))
+ .startedAt(
+ Optional.ofNullable(run.getStartedAt())
+ .map(Timestamp::toInstant)
+ .map(Instant::toString)
+ .orElse(null))
+ .finishedAt(
+ Optional.ofNullable(run.getFinishedAt())
+ .map(Timestamp::toInstant)
+ .map(Instant::toString)
+ .orElse(null))
+ .createdAt(run.getCreatedAt().toInstant().toString())
+ .updatedAt(run.getUpdatedAt().toInstant().toString())
+ .build();
+ }
+
+ public Run fromCreateDTO(RunCreateDTO reqDto, Plan plan) {
+ final Timestamp now = now();
+ final Long version = now.toInstant().toEpochMilli();
+ return Run.builder()
+ .uuid(UUID.randomUUID())
+ .displayName(reqDto.getDisplayName())
+ .note(reqDto.getNote())
+ .status(reqDto.getShouldStartAt() == null
+ ? RunStatus.PREPARED
+ : RunStatus.SCHEDULED)
+ .plan(plan)
+ .shouldStartAt(
+ Optional.ofNullable(reqDto.getShouldStartAt())
+ .map(Timestamp::from)
+ .orElse(null))
+ .startedAt(null)
+ .finishedAt(null)
+ .createdAt(now)
+ .updatedAt(now)
+ .version(version)
+ .build();
+ }
+
+ public Run fromUpdateDTO(RunUpdateDTO reqDto, Run run) {
+ final Timestamp now = now();
+ final Long version = now.toInstant().toEpochMilli();
+ run.setDisplayName(reqDto.getDisplayName());
+ run.setNote(reqDto.getNote());
+ run.setUpdatedAt(now);
+ run.setVersion(version);
+ return run;
+ }
+}
diff --git a/src/main/java/org/fairdatatrain/trainhandler/service/run/RunService.java b/src/main/java/org/fairdatatrain/trainhandler/service/run/RunService.java
new file mode 100644
index 0000000..4298e00
--- /dev/null
+++ b/src/main/java/org/fairdatatrain/trainhandler/service/run/RunService.java
@@ -0,0 +1,130 @@
+/**
+ * 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.trainhandler.service.run;
+
+import lombok.RequiredArgsConstructor;
+import lombok.extern.slf4j.Slf4j;
+import org.fairdatatrain.trainhandler.api.dto.run.RunCreateDTO;
+import org.fairdatatrain.trainhandler.api.dto.run.RunDTO;
+import org.fairdatatrain.trainhandler.api.dto.run.RunSimpleDTO;
+import org.fairdatatrain.trainhandler.api.dto.run.RunUpdateDTO;
+import org.fairdatatrain.trainhandler.data.model.Job;
+import org.fairdatatrain.trainhandler.data.model.Plan;
+import org.fairdatatrain.trainhandler.data.model.Run;
+import org.fairdatatrain.trainhandler.data.repository.JobRepository;
+import org.fairdatatrain.trainhandler.data.repository.RunRepository;
+import org.fairdatatrain.trainhandler.exception.NotFoundException;
+import org.fairdatatrain.trainhandler.service.async.RunNotificationListener;
+import org.fairdatatrain.trainhandler.service.job.JobMapper;
+import org.fairdatatrain.trainhandler.service.plan.PlanService;
+import org.springframework.data.domain.Page;
+import org.springframework.data.domain.Pageable;
+import org.springframework.stereotype.Service;
+import org.springframework.transaction.annotation.Transactional;
+import org.springframework.web.context.request.async.DeferredResult;
+
+import javax.persistence.EntityManager;
+import javax.persistence.PersistenceContext;
+import java.util.List;
+import java.util.UUID;
+
+import static java.lang.String.format;
+
+@Service
+@RequiredArgsConstructor
+@Slf4j
+public class RunService {
+
+ public static final String ENTITY_NAME = "Run";
+
+ private final RunRepository runRepository;
+
+ private final RunMapper runMapper;
+
+ private final PlanService planService;
+
+ private final JobMapper jobMapper;
+
+ private final JobRepository jobRepository;
+
+ private final RunNotificationListener runNotificationListener;
+
+ @PersistenceContext
+ private final EntityManager entityManager;
+
+ public Run getByIdOrThrow(UUID uuid) throws NotFoundException {
+ return runRepository
+ .findById(uuid)
+ .orElseThrow(() -> new NotFoundException(ENTITY_NAME, uuid));
+ }
+
+ public Page getRunsForPlanUuid(UUID planUuid, Pageable pageable) {
+ return runRepository.findAllByPlanUuid(planUuid, pageable).map(runMapper::toSimpleDTO);
+ }
+
+ public RunDTO getSingle(UUID uuid) throws NotFoundException {
+ final Run run = getByIdOrThrow(uuid);
+ return runMapper.toDTO(run);
+ }
+
+ @Transactional
+ public RunDTO create(RunCreateDTO reqDto) throws NotFoundException {
+ final Plan plan = planService.getByIdOrThrow(reqDto.getPlanUuid());
+ final Run newRun = runRepository.save(runMapper.fromCreateDTO(reqDto, plan));
+ entityManager.flush();
+ entityManager.refresh(newRun);
+ final List jobs = plan.getTargets().stream()
+ .map(target -> jobMapper.fromTarget(newRun, target))
+ .toList();
+ jobRepository.saveAllAndFlush(jobs);
+ entityManager.flush();
+ entityManager.refresh(newRun);
+ newRun.getJobs().forEach(entityManager::refresh);
+ return runMapper.toDTO(newRun);
+ }
+
+ @Transactional
+ public RunDTO update(UUID uuid, RunUpdateDTO reqDto) throws NotFoundException {
+ // TODO: abort (?)
+ final Run run = getByIdOrThrow(uuid);
+ final Run updatedRun =
+ runRepository.saveAndFlush(runMapper.fromUpdateDTO(reqDto, run));
+ return runMapper.toDTO(updatedRun);
+ }
+
+ @Transactional
+ public void poll(
+ UUID runUuid,
+ DeferredResult result,
+ Long version,
+ RunDTO currentRun
+ ) {
+ log.info(format("REQUESTED VERSION: %s", version));
+ log.info(format("CURRENT VERSION: %s", currentRun.getVersion()));
+ if (version < currentRun.getVersion()) {
+ result.setResult(currentRun);
+ }
+ log.info("No run update at this point, enqueueing...");
+ runNotificationListener.enqueue(runUuid, version, result);
+ }
+}
diff --git a/src/main/java/org/fairdatatrain/trainhandler/service/station/StationMapper.java b/src/main/java/org/fairdatatrain/trainhandler/service/station/StationMapper.java
new file mode 100644
index 0000000..c750aee
--- /dev/null
+++ b/src/main/java/org/fairdatatrain/trainhandler/service/station/StationMapper.java
@@ -0,0 +1,158 @@
+/**
+ * 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.trainhandler.service.station;
+
+import lombok.RequiredArgsConstructor;
+import org.fairdatatrain.trainhandler.api.dto.station.StationCreateDTO;
+import org.fairdatatrain.trainhandler.api.dto.station.StationDTO;
+import org.fairdatatrain.trainhandler.api.dto.station.StationSimpleDTO;
+import org.fairdatatrain.trainhandler.api.dto.station.StationUpdateDTO;
+import org.fairdatatrain.trainhandler.data.model.Station;
+import org.fairdatatrain.trainhandler.data.model.TrainType;
+import org.fairdatatrain.trainhandler.data.model.enums.SyncItemStatus;
+import org.fairdatatrain.trainhandler.service.stationdirectory.StationDirectoryMapper;
+import org.fairdatatrain.trainhandler.service.traintype.TrainTypeMapper;
+import org.springframework.stereotype.Component;
+
+import java.sql.Timestamp;
+import java.time.Instant;
+import java.util.Arrays;
+import java.util.List;
+import java.util.Optional;
+
+import static org.fairdatatrain.trainhandler.utils.TimeUtils.now;
+
+@Component
+@RequiredArgsConstructor
+public class StationMapper {
+
+ private static final String KEYWORD_SEP = ",";
+
+ private final StationDirectoryMapper stationDirectoryMapper;
+
+ private final TrainTypeMapper trainTypeMapper;
+
+ public StationDTO toDTO(Station station) {
+ return StationDTO.builder()
+ .uuid(station.getUuid())
+ .title(station.getTitle())
+ .uri(station.getUri())
+ .description(station.getDescription())
+ .keywords(
+ Arrays.stream(station.getKeywords().split(KEYWORD_SEP))
+ .map(String::trim)
+ .filter(item -> !item.isBlank())
+ .toList())
+ .endpointUrl(station.getEndpointUrl())
+ .endpointDescription(station.getEndpointDescription())
+ .status(station.getStatus())
+ .softDeleted(station.getSoftDeleted())
+ .metadata(station.getMetadata())
+ .directory(
+ Optional.ofNullable(station.getDirectory())
+ .map(stationDirectoryMapper::toSimpleDTO)
+ .orElse(null)
+ )
+ .types(station.getTypes().stream().map(trainTypeMapper::toSimpleDTO).toList())
+ .lastContactAt(
+ Optional.ofNullable(station.getLastContactAt())
+ .map(Timestamp::toInstant)
+ .map(Instant::toString)
+ .orElse(null)
+ )
+ .createdAt(station.getCreatedAt().toInstant().toString())
+ .updatedAt(station.getUpdatedAt().toInstant().toString())
+ .build();
+ }
+
+ public StationSimpleDTO toSimpleDTO(Station station) {
+ return StationSimpleDTO.builder()
+ .uuid(station.getUuid())
+ .title(station.getTitle())
+ .uri(station.getUri())
+ .keywords(
+ Arrays.stream(station.getKeywords().split(KEYWORD_SEP))
+ .map(String::trim)
+ .filter(item -> !item.isBlank())
+ .toList())
+ .endpointUrl(station.getEndpointUrl())
+ .endpointDescription(station.getEndpointDescription())
+ .status(station.getStatus())
+ .directory(
+ Optional.ofNullable(station.getDirectory())
+ .map(stationDirectoryMapper::toSimpleDTO)
+ .orElse(null)
+ )
+ .types(station.getTypes().stream().map(trainTypeMapper::toSimpleDTO).toList())
+ .build();
+ }
+
+ public Station fromUpdateDTO(StationUpdateDTO dto, Station station, List trainTypes) {
+ return station.toBuilder()
+ .uri(dto.getUri())
+ .title(dto.getTitle())
+ .description(dto.getDescription())
+ .keywords(String.join(KEYWORD_SEP, dto.getKeywords()))
+ .endpointUrl(dto.getEndpointUrl())
+ .endpointDescription(dto.getEndpointDescription())
+ .metadata(dto.getMetadata())
+ .types(trainTypes)
+ .softDeleted(
+ Optional.ofNullable(dto.getSoftDeleted())
+ .orElse(station.getSoftDeleted())
+ )
+ .updatedAt(now())
+ .build();
+ }
+
+ public Station fromCreateDTO(StationCreateDTO dto, List trainTypes) {
+ final Timestamp now = now();
+ return Station.builder()
+ .uri(dto.getUri())
+ .title(dto.getTitle())
+ .description(dto.getDescription())
+ .keywords(String.join(KEYWORD_SEP, dto.getKeywords()))
+ .endpointUrl(dto.getEndpointUrl())
+ .endpointDescription(dto.getEndpointDescription())
+ .metadata(dto.getMetadata())
+ .types(trainTypes)
+ .status(SyncItemStatus.SYNCED)
+ .softDeleted(false)
+ .createdAt(now)
+ .updatedAt(now)
+ .build();
+ }
+
+ public Station updateFetch(Station station, Station fetchedStation) {
+ return station.toBuilder()
+ .title(fetchedStation.getTitle())
+ .description(fetchedStation.getDescription())
+ .keywords(fetchedStation.getKeywords())
+ .metadata(fetchedStation.getMetadata())
+ .lastContactAt(fetchedStation.getLastContactAt())
+ .types(fetchedStation.getTypes())
+ .status(fetchedStation.getStatus())
+ .updatedAt(now())
+ .build();
+ }
+}
diff --git a/src/main/java/org/fairdatatrain/trainhandler/service/station/StationService.java b/src/main/java/org/fairdatatrain/trainhandler/service/station/StationService.java
new file mode 100644
index 0000000..7e3cf8a
--- /dev/null
+++ b/src/main/java/org/fairdatatrain/trainhandler/service/station/StationService.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.trainhandler.service.station;
+
+import lombok.RequiredArgsConstructor;
+import org.fairdatatrain.trainhandler.api.dto.station.StationCreateDTO;
+import org.fairdatatrain.trainhandler.api.dto.station.StationDTO;
+import org.fairdatatrain.trainhandler.api.dto.station.StationSimpleDTO;
+import org.fairdatatrain.trainhandler.api.dto.station.StationUpdateDTO;
+import org.fairdatatrain.trainhandler.api.dto.train.TrainSimpleDTO;
+import org.fairdatatrain.trainhandler.data.model.Station;
+import org.fairdatatrain.trainhandler.data.model.Train;
+import org.fairdatatrain.trainhandler.data.model.TrainType;
+import org.fairdatatrain.trainhandler.data.repository.StationRepository;
+import org.fairdatatrain.trainhandler.exception.NotFoundException;
+import org.fairdatatrain.trainhandler.service.stationdirectory.StationDirectoryIndexer;
+import org.fairdatatrain.trainhandler.service.train.TrainMapper;
+import org.fairdatatrain.trainhandler.service.traintype.TrainTypeService;
+import org.springframework.data.domain.Page;
+import org.springframework.data.domain.Pageable;
+import org.springframework.stereotype.Service;
+import org.springframework.transaction.annotation.Transactional;
+
+import java.util.*;
+
+@Service
+@RequiredArgsConstructor
+public class StationService {
+
+ private static final String ENTITY_NAME = "Station";
+
+ private final StationRepository stationRepository;
+
+ private final StationMapper stationMapper;
+
+ private final TrainMapper trainMapper;
+
+ private final TrainTypeService trainTypeService;
+
+ private final StationDirectoryIndexer stationDirectoryIndexer;
+
+ public Station getByIdOrThrow(UUID uuid) throws NotFoundException {
+ return stationRepository
+ .findById(uuid)
+ .orElseThrow(() -> new NotFoundException(ENTITY_NAME, uuid));
+ }
+
+ public Page getPaged(String query, Pageable pageable) {
+ if (query.isBlank()) {
+ return stationRepository
+ .findAllBySoftDeletedIsFalse(pageable)
+ .map(stationMapper::toSimpleDTO);
+ }
+ return stationRepository
+ .findByTitleContainingIgnoreCaseAndSoftDeletedIsFalse(query, pageable)
+ .map(stationMapper::toSimpleDTO);
+ }
+
+ public StationDTO getSingle(UUID uuid) throws NotFoundException {
+ final Station station = getByIdOrThrow(uuid);
+ return stationMapper.toDTO(station);
+ }
+
+ public List getAll(String query) {
+ return stationRepository
+ .findByTitleContainingIgnoreCase(query)
+ .stream()
+ .map(stationMapper::toSimpleDTO)
+ .toList();
+ }
+
+ public List getSuitableStations(UUID uuid) throws NotFoundException {
+ final Station station = getByIdOrThrow(uuid);
+ final Set suitableTrains = new HashSet<>();
+ station.getTypes().stream().map(TrainType::getTrains).forEach(suitableTrains::addAll);
+ return suitableTrains.stream().map(trainMapper::toSimpleDTO).toList();
+ }
+
+ @Transactional
+ public StationDTO update(UUID uuid, StationUpdateDTO dto) throws NotFoundException {
+ final Station station = getByIdOrThrow(uuid);
+ final List trainTypes =
+ trainTypeService.getTrainTypes(dto.getTrainTypeUuids());
+ Station newStation = stationRepository
+ .saveAndFlush(stationMapper.fromUpdateDTO(dto, station, trainTypes));
+ if (dto.getFetch()) {
+ newStation = updateFetch(newStation);
+ }
+ return stationMapper.toDTO(newStation);
+ }
+
+ @Transactional
+ public StationDTO softDelete(UUID uuid) throws NotFoundException {
+ final Station station = getByIdOrThrow(uuid);
+ station.setSoftDeleted(true);
+ return stationMapper.toDTO(stationRepository.save(station));
+ }
+
+ public StationDTO create(StationCreateDTO dto) {
+ final List trainTypes =
+ trainTypeService.getTrainTypes(dto.getTrainTypeUuids());
+ Station newStation = stationRepository
+ .saveAndFlush(stationMapper.fromCreateDTO(dto, trainTypes));
+ if (dto.getFetch()) {
+ newStation = updateFetch(newStation);
+ }
+ return stationMapper.toDTO(newStation);
+ }
+
+ private Station updateFetch(Station station) {
+ final Station fetchedStation = stationDirectoryIndexer.tryToFetchStation(station.getUri());
+ if (fetchedStation != null) {
+ return stationRepository
+ .saveAndFlush(stationMapper.updateFetch(station, fetchedStation));
+ }
+ return station;
+ }
+}
diff --git a/src/main/java/org/fairdatatrain/trainhandler/service/stationdirectory/StationDirectoryIndexer.java b/src/main/java/org/fairdatatrain/trainhandler/service/stationdirectory/StationDirectoryIndexer.java
new file mode 100644
index 0000000..7c363f2
--- /dev/null
+++ b/src/main/java/org/fairdatatrain/trainhandler/service/stationdirectory/StationDirectoryIndexer.java
@@ -0,0 +1,284 @@
+/**
+ * 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.trainhandler.service.stationdirectory;
+
+import lombok.RequiredArgsConstructor;
+import lombok.extern.slf4j.Slf4j;
+import nl.dtls.fairdatapoint.vocabulary.FDT;
+import org.eclipse.rdf4j.model.IRI;
+import org.eclipse.rdf4j.model.Model;
+import org.eclipse.rdf4j.model.Resource;
+import org.eclipse.rdf4j.model.Value;
+import org.eclipse.rdf4j.model.vocabulary.DCAT;
+import org.eclipse.rdf4j.model.vocabulary.RDF;
+import org.eclipse.rdf4j.model.vocabulary.RDFS;
+import org.fairdatatrain.trainhandler.data.model.*;
+import org.fairdatatrain.trainhandler.data.model.enums.SyncItemStatus;
+import org.fairdatatrain.trainhandler.data.model.enums.SyncServiceStatus;
+import org.fairdatatrain.trainhandler.data.repository.*;
+import org.fairdatatrain.trainhandler.service.indexing.BaseIndexer;
+import org.fairdatatrain.trainhandler.utils.ValueFactoryUtils;
+import org.springframework.stereotype.Service;
+import org.springframework.transaction.annotation.Transactional;
+
+import java.util.*;
+import java.util.function.Function;
+import java.util.stream.Collectors;
+
+import static java.lang.String.format;
+import static org.fairdatatrain.trainhandler.utils.RdfIOUtils.write;
+import static org.fairdatatrain.trainhandler.utils.RdfUtils.getObjectsBy;
+import static org.fairdatatrain.trainhandler.utils.RdfUtils.getStringObjectBy;
+import static org.fairdatatrain.trainhandler.utils.TimeUtils.now;
+import static org.fairdatatrain.trainhandler.utils.ValueFactoryUtils.i;
+
+@Slf4j
+@Service
+@RequiredArgsConstructor
+public class StationDirectoryIndexer {
+
+ // TODO: from config/ontology
+ private static final Map> MECHANISM_TRAIN_TYPES = new HashMap<>();
+
+ private final StationDirectoryRepository stationDirectoryRepository;
+
+ private final TrainTypeRepository trainTypeRepository;
+
+ private final StationRepository stationRepository;
+
+ private final BaseIndexer baseIndexer;
+
+ static {
+ MECHANISM_TRAIN_TYPES.put(FDT.SPARQL, List.of(FDT.SPARQLTRAIN));
+ MECHANISM_TRAIN_TYPES.put(FDT.PYTHON, List.of(FDT.PYTHONTRAIN));
+ MECHANISM_TRAIN_TYPES.put(FDT.DOCKER, List.of(FDT.DOCKERTRAIN));
+ MECHANISM_TRAIN_TYPES.put(FDT.GRAPHQL, List.of(FDT.GRAPHQLTRAIN));
+ MECHANISM_TRAIN_TYPES.put(FDT.API, List.of(FDT.APITRAIN));
+ MECHANISM_TRAIN_TYPES.put(FDT.SCRIPT, List.of(FDT.SCRIPTTRAIN));
+ MECHANISM_TRAIN_TYPES.put(FDT.SINGULARITY, List.of(FDT.SINGULARITYTRAIN));
+ MECHANISM_TRAIN_TYPES.put(FDT.FHIRAPI, List.of(FDT.FHIRTRAIN));
+ MECHANISM_TRAIN_TYPES.put(FDT.EXECUTABLECONTAINER, List.of(FDT.CONTAINERTRAIN));
+ MECHANISM_TRAIN_TYPES.put(FDT.R, List.of(FDT.RTRAIN));
+ MECHANISM_TRAIN_TYPES.put(FDT.QUERY, List.of(FDT.QUERYTRAIN));
+ MECHANISM_TRAIN_TYPES.put(FDT.SQL, List.of(FDT.SQLTRAIN));
+ MECHANISM_TRAIN_TYPES.put(FDT.RESTAPI, List.of(FDT.RESTTRAIN));
+ }
+
+ @Transactional
+ public void indexDirectory(StationDirectory stationDirectory) {
+ final Set visitedUris = new HashSet<>();
+ final Queue toVisitUris = new LinkedList<>();
+ final List stations = new ArrayList<>();
+ final List trainTypes = trainTypeRepository.findAll();
+
+ Model model;
+ try {
+ model = baseIndexer.makeRequest(stationDirectory.getUri());
+ updateDirectory(stationDirectory, model);
+ stations.addAll(extractStations(stationDirectory, model, trainTypes));
+ toVisitUris.addAll(baseIndexer.extractChildren(model));
+ }
+ catch (Exception exception) {
+ updateFaultyDirectory(stationDirectory);
+ }
+
+ while (!toVisitUris.isEmpty()) {
+ final String uri = toVisitUris.poll();
+ log.debug(format("Indexing garage (%s): traversing %s",
+ stationDirectory.getUri(), uri));
+ if (visitedUris.contains(uri)) {
+ continue;
+ }
+ else {
+ visitedUris.add(uri);
+ }
+ try {
+ model = baseIndexer.makeRequest(uri);
+ stations.addAll(extractStations(stationDirectory, model, trainTypes));
+ toVisitUris.addAll(baseIndexer.extractChildren(model));
+ }
+ catch (Exception exception) {
+ log.debug(format("Skipping %s (exception: %s)", uri, exception));
+ }
+ }
+
+ updateStations(stationDirectory, stations);
+ }
+
+ public Station tryToFetchStation(String uri) {
+ final List trainTypes = trainTypeRepository.findAll();
+ try {
+ final Model model = baseIndexer.makeRequest(uri);
+ final List stations = extractStations(null, model, trainTypes);
+ if (stations.size() > 0) {
+ return stations.get(0);
+ }
+ }
+ catch (Exception exception) {
+ log.debug(format("Failed to fetch %s (exception: %s)", uri, exception));
+ }
+ return null;
+ }
+
+ private void updateDirectory(StationDirectory stationDirectory, Model model) {
+ stationDirectory.setMetadata("");
+ stationDirectory.setLastContactAt(now());
+ stationDirectory.setStatus(SyncServiceStatus.SYNCED);
+ stationDirectoryRepository.saveAndFlush(stationDirectory);
+ }
+
+ private void updateFaultyDirectory(StationDirectory stationDirectory) {
+ stationDirectory.setStatus(SyncServiceStatus.UNREACHABLE);
+ stationDirectoryRepository.saveAndFlush(stationDirectory);
+ }
+
+ private void updateStations(StationDirectory stationDirectory, List stations) {
+ final List stationsToSave = new ArrayList<>();
+ final Map existingStations = stationDirectory
+ .getStations()
+ .stream()
+ .collect(Collectors.toMap(Station::getUri, Function.identity()));
+ final Map currentStations = stations
+ .stream()
+ .collect(Collectors.toMap(Station::getUri, Function.identity()));
+
+ currentStations.forEach((uri, station) -> {
+ if (existingStations.containsKey(uri)) {
+ updateStation(existingStations.get(uri), station);
+ }
+ else {
+ station.setDirectory(stationDirectory);
+ stationDirectory.getStations().add(station);
+ stationsToSave.add(station);
+ }
+ });
+ existingStations.forEach((uri, station) -> {
+ if (!currentStations.containsKey(uri)) {
+ deprecateStation(station);
+ }
+ });
+
+ stationRepository.saveAllAndFlush(stationsToSave);
+ }
+
+ private void deprecateStation(Station existingStation) {
+ existingStation.setStatus(SyncItemStatus.DELETED);
+ existingStation.setUpdatedAt(now());
+ }
+
+ private void updateStation(Station existingStation, Station newStation) {
+ existingStation.setTitle(newStation.getTitle());
+ existingStation.setDescription(newStation.getDescription());
+ existingStation.setKeywords(newStation.getKeywords());
+ existingStation.setTypes(newStation.getTypes());
+ existingStation.setMetadata(newStation.getMetadata());
+ existingStation.setLastContactAt(newStation.getLastContactAt());
+ existingStation.setUpdatedAt(now());
+ }
+
+ private List extractStations(
+ StationDirectory stationDirectory,
+ Model model,
+ List trainTypes
+ ) {
+ return model
+ .filter(null, RDF.TYPE, i(FDT.DATASTATION))
+ .parallelStream()
+ .map(stmt -> extractStation(stationDirectory, stmt.getSubject(), model, trainTypes))
+ .filter(Objects::nonNull)
+ .toList();
+ }
+
+ private Station extractStation(
+ StationDirectory stationDirectory,
+ Resource resource,
+ Model model,
+ List trainTypes
+ ) {
+ log.info(format("Station found: %s", resource));
+ final String title = getStringObjectBy(model, resource, RDFS.LABEL);
+ final String description = getStringObjectBy(model, resource, RDFS.COMMENT);
+ final String endpointUrl = getStringObjectBy(model, resource, DCAT.ENDPOINT_URL);
+ final String endpointDescription = getStringObjectBy(model, resource, DCAT.ENDPOINT_DESCRIPTION);
+ final List keywords = getObjectsBy(model, resource, DCAT.KEYWORD)
+ .stream()
+ .map(Value::stringValue)
+ .toList();
+
+ final Set interactionMechanisms =
+ getObjectsBy(model, resource, FDT.IMPLEMENTSINTERACTIONMECHANISM)
+ .stream()
+ .map(Value::stringValue)
+ .map(ValueFactoryUtils::i)
+ .collect(Collectors.toSet());
+ final Set trainTypeUris = interactionMechanisms
+ .stream()
+ .map(this::getTrainsByInteractionMechanism)
+ .flatMap(List::stream)
+ .map(IRI::stringValue)
+ .collect(Collectors.toSet());
+
+ if (title == null) {
+ log.warn(format("Skipping station %s (missing required information: title)", resource));
+ return null;
+ }
+ if (endpointUrl == null) {
+ log.warn(format("Skipping station %s (missing required information: endpoint URL)", resource));
+ return null;
+ }
+
+ final List matchingTrainTypes = trainTypes
+ .stream()
+ .filter(trainType -> trainTypeUris.contains(trainType.getUri()))
+ .toList();
+
+ if (matchingTrainTypes.isEmpty()) {
+ log.warn(format("No matching train type found: %s", trainTypeUris));
+ return null;
+ }
+
+ return Station
+ .builder()
+ .uuid(UUID.randomUUID())
+ .uri(resource.stringValue())
+ .title(title)
+ .description(description == null ? "" : description)
+ .keywords(String.join(",", keywords))
+ .endpointUrl(endpointUrl)
+ .endpointDescription(endpointDescription == null ? "" : endpointDescription)
+ .metadata(write(model))
+ .types(matchingTrainTypes)
+ .directory(stationDirectory)
+ .status(SyncItemStatus.SYNCED)
+ .lastContactAt(now())
+ .createdAt(now())
+ .updatedAt(now())
+ .softDeleted(false)
+ .build();
+ }
+
+ private List getTrainsByInteractionMechanism(IRI interactionMechanism) {
+ return Optional.ofNullable(MECHANISM_TRAIN_TYPES.get(interactionMechanism))
+ .orElse(Collections.emptyList());
+ }
+}
diff --git a/src/main/java/org/fairdatatrain/trainhandler/service/stationdirectory/StationDirectoryMapper.java b/src/main/java/org/fairdatatrain/trainhandler/service/stationdirectory/StationDirectoryMapper.java
new file mode 100644
index 0000000..ffd532b
--- /dev/null
+++ b/src/main/java/org/fairdatatrain/trainhandler/service/stationdirectory/StationDirectoryMapper.java
@@ -0,0 +1,91 @@
+/**
+ * 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.trainhandler.service.stationdirectory;
+
+import org.fairdatatrain.trainhandler.api.dto.stationdirectory.StationDirectoryChangeDTO;
+import org.fairdatatrain.trainhandler.api.dto.stationdirectory.StationDirectoryDTO;
+import org.fairdatatrain.trainhandler.api.dto.stationdirectory.StationDirectorySimpleDTO;
+import org.fairdatatrain.trainhandler.data.model.StationDirectory;
+import org.fairdatatrain.trainhandler.data.model.enums.SyncServiceStatus;
+import org.springframework.stereotype.Component;
+
+import java.sql.Timestamp;
+import java.time.Instant;
+import java.util.Optional;
+
+@Component
+public class StationDirectoryMapper {
+ public StationDirectoryDTO toDTO(StationDirectory stationDirectory) {
+ return StationDirectoryDTO.builder()
+ .uuid(stationDirectory.getUuid())
+ .uri(stationDirectory.getUri())
+ .displayName(stationDirectory.getDisplayName())
+ .metadata(stationDirectory.getMetadata())
+ .note(stationDirectory.getNote())
+ .status(stationDirectory.getStatus())
+ .deletable(stationDirectory.isDeletable())
+ .lastContactAt(
+ Optional.ofNullable(stationDirectory.getLastContactAt())
+ .map(Timestamp::toInstant)
+ .map(Instant::toString)
+ .orElse(null))
+ .createdAt(stationDirectory.getCreatedAt().toInstant().toString())
+ .updatedAt(stationDirectory.getUpdatedAt().toInstant().toString())
+ .build();
+ }
+
+ public StationDirectory fromCreateDTO(StationDirectoryChangeDTO reqDto) {
+ final Timestamp now = Timestamp.from(Instant.now());
+ return StationDirectory.builder()
+ .uri(reqDto.getUri())
+ .displayName(reqDto.getDisplayName())
+ .note(reqDto.getNote())
+ .status(SyncServiceStatus.SYNCING)
+ .metadata(null)
+ .lastContactAt(null)
+ .createdAt(now)
+ .updatedAt(now)
+ .build();
+ }
+
+ public StationDirectory fromUpdateDTO(
+ StationDirectoryChangeDTO reqDto,
+ StationDirectory stationDirectory
+ ) {
+ final Timestamp now = Timestamp.from(Instant.now());
+ stationDirectory.setUri(reqDto.getUri());
+ stationDirectory.setDisplayName(reqDto.getDisplayName());
+ stationDirectory.setNote(reqDto.getNote());
+ stationDirectory.setStatus(SyncServiceStatus.SYNCING);
+ stationDirectory.setUpdatedAt(now);
+ return stationDirectory;
+ }
+
+ public StationDirectorySimpleDTO toSimpleDTO(StationDirectory stationDirectory) {
+ return StationDirectorySimpleDTO.builder()
+ .uuid(stationDirectory.getUuid())
+ .uri(stationDirectory.getUri())
+ .displayName(stationDirectory.getDisplayName())
+ .build();
+ }
+}
diff --git a/src/main/java/org/fairdatatrain/trainhandler/service/stationdirectory/StationDirectoryService.java b/src/main/java/org/fairdatatrain/trainhandler/service/stationdirectory/StationDirectoryService.java
new file mode 100644
index 0000000..100ba57
--- /dev/null
+++ b/src/main/java/org/fairdatatrain/trainhandler/service/stationdirectory/StationDirectoryService.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.trainhandler.service.stationdirectory;
+
+import lombok.RequiredArgsConstructor;
+import org.fairdatatrain.trainhandler.api.dto.stationdirectory.StationDirectoryChangeDTO;
+import org.fairdatatrain.trainhandler.api.dto.stationdirectory.StationDirectoryDTO;
+import org.fairdatatrain.trainhandler.data.model.StationDirectory;
+import org.fairdatatrain.trainhandler.data.model.enums.SyncServiceStatus;
+import org.fairdatatrain.trainhandler.data.repository.StationDirectoryRepository;
+import org.fairdatatrain.trainhandler.exception.NotFoundException;
+import org.springframework.data.domain.Page;
+import org.springframework.data.domain.Pageable;
+import org.springframework.scheduling.annotation.Async;
+import org.springframework.stereotype.Service;
+import org.springframework.transaction.annotation.Transactional;
+
+import java.util.UUID;
+
+@Service
+@RequiredArgsConstructor
+public class StationDirectoryService {
+
+ private static final String ENTITY_NAME = "StationDirectory";
+
+ private final StationDirectoryRepository stationDirectoryRepository;
+
+ private final StationDirectoryMapper stationDirectoryMapper;
+
+ private final StationDirectoryIndexer stationDirectoryIndexer;
+
+ private StationDirectory getByIdOrThrow(UUID uuid) throws NotFoundException {
+ return stationDirectoryRepository
+ .findById(uuid)
+ .orElseThrow(() -> new NotFoundException(ENTITY_NAME, uuid));
+ }
+
+ public void delete(UUID uuid) throws NotFoundException {
+ final StationDirectory stationDirectory = getByIdOrThrow(uuid);
+ stationDirectoryRepository.delete(stationDirectory);
+ }
+
+ public Page getPaged(String query, Pageable pageable) {
+ if (query.isBlank()) {
+ return stationDirectoryRepository.findAll(pageable).map(stationDirectoryMapper::toDTO);
+ }
+ return stationDirectoryRepository
+ .findByDisplayNameContainingIgnoreCase(query, pageable)
+ .map(stationDirectoryMapper::toDTO);
+ }
+
+ public StationDirectoryDTO getSingle(UUID uuid) throws NotFoundException {
+ final StationDirectory stationDirectory = getByIdOrThrow(uuid);
+ return stationDirectoryMapper.toDTO(stationDirectory);
+ }
+
+ @Transactional
+ public StationDirectoryDTO create(StationDirectoryChangeDTO reqDto) {
+ // TODO: validate?
+ final StationDirectory newStationDirectory =
+ stationDirectoryRepository.saveAndFlush(
+ stationDirectoryMapper.fromCreateDTO(reqDto));
+ return this.stationDirectoryMapper.toDTO(newStationDirectory);
+ }
+
+ @Transactional
+ public StationDirectoryDTO update(UUID uuid, StationDirectoryChangeDTO reqDto)
+ throws NotFoundException {
+ // TODO: validate?
+ final StationDirectory stationDirectory = getByIdOrThrow(uuid);
+ final StationDirectory updatedStationDirectory =
+ stationDirectoryRepository.saveAndFlush(
+ stationDirectoryMapper.fromUpdateDTO(reqDto, stationDirectory));
+ return this.stationDirectoryMapper.toDTO(updatedStationDirectory);
+ }
+
+ @Transactional
+ public void reindex(UUID uuid) throws NotFoundException {
+ final StationDirectory stationDirectory = getByIdOrThrow(uuid);
+ stationDirectory.setStatus(SyncServiceStatus.SYNCING);
+ final StationDirectory updatedStationDirectory =
+ stationDirectoryRepository.saveAndFlush(stationDirectory);
+ triggerAsyncIndexing(updatedStationDirectory);
+ }
+
+ @Async
+ protected void triggerAsyncIndexing(StationDirectory stationDirectory) {
+ stationDirectoryIndexer.indexDirectory(stationDirectory);
+ }
+}
diff --git a/src/main/java/org/fairdatatrain/trainhandler/service/train/TrainMapper.java b/src/main/java/org/fairdatatrain/trainhandler/service/train/TrainMapper.java
new file mode 100644
index 0000000..b7f5365
--- /dev/null
+++ b/src/main/java/org/fairdatatrain/trainhandler/service/train/TrainMapper.java
@@ -0,0 +1,150 @@
+/**
+ * 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.trainhandler.service.train;
+
+import lombok.RequiredArgsConstructor;
+import org.fairdatatrain.trainhandler.api.dto.train.TrainCreateDTO;
+import org.fairdatatrain.trainhandler.api.dto.train.TrainDTO;
+import org.fairdatatrain.trainhandler.api.dto.train.TrainSimpleDTO;
+import org.fairdatatrain.trainhandler.api.dto.train.TrainUpdateDTO;
+import org.fairdatatrain.trainhandler.data.model.Train;
+import org.fairdatatrain.trainhandler.data.model.TrainType;
+import org.fairdatatrain.trainhandler.data.model.enums.SyncItemStatus;
+import org.fairdatatrain.trainhandler.service.traingarage.TrainGarageMapper;
+import org.fairdatatrain.trainhandler.service.traintype.TrainTypeMapper;
+import org.springframework.stereotype.Component;
+
+import java.sql.Timestamp;
+import java.time.Instant;
+import java.util.Arrays;
+import java.util.List;
+import java.util.Optional;
+
+import static org.fairdatatrain.trainhandler.utils.TimeUtils.now;
+
+@Component
+@RequiredArgsConstructor
+public class TrainMapper {
+
+ private static final String KEYWORD_SEP = ",";
+
+ private final TrainGarageMapper trainGarageMapper;
+
+ private final TrainTypeMapper trainTypeMapper;
+
+ public TrainDTO toDTO(Train train) {
+ return TrainDTO.builder()
+ .uuid(train.getUuid())
+ .title(train.getTitle())
+ .uri(train.getUri())
+ .description(train.getDescription())
+ .keywords(
+ Arrays.stream(train.getKeywords().split(KEYWORD_SEP))
+ .map(String::trim)
+ .filter(item -> !item.isBlank())
+ .toList())
+ .status(train.getStatus())
+ .softDeleted(train.getSoftDeleted())
+ .metadata(train.getMetadata())
+ .garage(
+ Optional.ofNullable(train.getGarage())
+ .map(trainGarageMapper::toSimpleDTO)
+ .orElse(null)
+ )
+ .types(train.getTypes().stream().map(trainTypeMapper::toSimpleDTO).toList())
+ .lastContactAt(
+ Optional.ofNullable(train.getLastContactAt())
+ .map(Timestamp::toInstant)
+ .map(Instant::toString)
+ .orElse(null)
+ )
+ .createdAt(train.getCreatedAt().toInstant().toString())
+ .updatedAt(train.getUpdatedAt().toInstant().toString())
+ .build();
+ }
+
+ public TrainSimpleDTO toSimpleDTO(Train train) {
+ return TrainSimpleDTO.builder()
+ .uuid(train.getUuid())
+ .title(train.getTitle())
+ .uri(train.getUri())
+ .keywords(
+ Arrays.stream(train.getKeywords().split(KEYWORD_SEP))
+ .map(String::trim)
+ .filter(item -> !item.isBlank())
+ .toList())
+ .status(train.getStatus())
+ .garage(
+ Optional.ofNullable(train.getGarage())
+ .map(trainGarageMapper::toSimpleDTO)
+ .orElse(null)
+ )
+ .types(train.getTypes().stream().map(trainTypeMapper::toSimpleDTO).toList())
+ .build();
+ }
+
+ public Train fromUpdateDTO(TrainUpdateDTO dto, Train train, List trainTypes) {
+ return train.toBuilder()
+ .uri(dto.getUri())
+ .title(dto.getTitle())
+ .description(dto.getDescription())
+ .keywords(String.join(KEYWORD_SEP, dto.getKeywords()))
+ .metadata(dto.getMetadata())
+ .types(trainTypes)
+ .softDeleted(
+ Optional.ofNullable(dto.getSoftDeleted())
+ .orElse(train.getSoftDeleted())
+ )
+ .updatedAt(now())
+ .build();
+ }
+
+ public Train fromCreateDTO(TrainCreateDTO dto, List trainTypes) {
+ final Timestamp now = now();
+ return Train.builder()
+ .uri(dto.getUri())
+ .title(dto.getTitle())
+ .description(dto.getDescription())
+ .keywords(String.join(KEYWORD_SEP, dto.getKeywords()))
+ .metadata(dto.getMetadata())
+ .types(trainTypes)
+ .status(SyncItemStatus.SYNCED)
+ .softDeleted(false)
+ .createdAt(now)
+ .updatedAt(now)
+ .build();
+ }
+
+ public Train updateFetch(Train train, Train fetchedTrain) {
+ return train.toBuilder()
+ .title(fetchedTrain.getTitle())
+ .description(fetchedTrain.getDescription())
+ .keywords(fetchedTrain.getKeywords())
+ .metadata(fetchedTrain.getMetadata())
+ .lastContactAt(fetchedTrain.getLastContactAt())
+ .types(fetchedTrain.getTypes())
+ .status(fetchedTrain.getStatus())
+ .updatedAt(now())
+ .build();
+ }
+}
diff --git a/src/main/java/org/fairdatatrain/trainhandler/service/train/TrainService.java b/src/main/java/org/fairdatatrain/trainhandler/service/train/TrainService.java
new file mode 100644
index 0000000..1f607d6
--- /dev/null
+++ b/src/main/java/org/fairdatatrain/trainhandler/service/train/TrainService.java
@@ -0,0 +1,142 @@
+/**
+ * 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.trainhandler.service.train;
+
+import lombok.RequiredArgsConstructor;
+import org.fairdatatrain.trainhandler.api.dto.station.StationSimpleDTO;
+import org.fairdatatrain.trainhandler.api.dto.train.TrainCreateDTO;
+import org.fairdatatrain.trainhandler.api.dto.train.TrainDTO;
+import org.fairdatatrain.trainhandler.api.dto.train.TrainSimpleDTO;
+import org.fairdatatrain.trainhandler.api.dto.train.TrainUpdateDTO;
+import org.fairdatatrain.trainhandler.data.model.Station;
+import org.fairdatatrain.trainhandler.data.model.Train;
+import org.fairdatatrain.trainhandler.data.model.TrainType;
+import org.fairdatatrain.trainhandler.data.repository.TrainRepository;
+import org.fairdatatrain.trainhandler.exception.NotFoundException;
+import org.fairdatatrain.trainhandler.service.station.StationMapper;
+import org.fairdatatrain.trainhandler.service.traingarage.TrainGarageIndexer;
+import org.fairdatatrain.trainhandler.service.traintype.TrainTypeService;
+import org.springframework.data.domain.Page;
+import org.springframework.data.domain.Pageable;
+import org.springframework.stereotype.Service;
+import org.springframework.transaction.annotation.Transactional;
+
+import java.util.HashSet;
+import java.util.List;
+import java.util.Set;
+import java.util.UUID;
+
+@Service
+@RequiredArgsConstructor
+public class TrainService {
+
+ private static final String ENTITY_NAME = "Train";
+
+ private final TrainRepository trainRepository;
+
+ private final TrainMapper trainMapper;
+
+ private final StationMapper stationMapper;
+
+ private final TrainTypeService trainTypeService;
+
+ private final TrainGarageIndexer trainGarageIndexer;
+
+ public Train getByIdOrThrow(UUID uuid) throws NotFoundException {
+ return trainRepository
+ .findById(uuid)
+ .orElseThrow(() -> new NotFoundException(ENTITY_NAME, uuid));
+ }
+
+ public Page getPaged(String query, Pageable pageable) {
+ if (query.isBlank()) {
+ return trainRepository
+ .findAllBySoftDeletedIsFalse(pageable)
+ .map(trainMapper::toSimpleDTO);
+ }
+ return trainRepository
+ .findByTitleContainingIgnoreCaseAndSoftDeletedIsFalse(query, pageable)
+ .map(trainMapper::toSimpleDTO);
+ }
+
+ public TrainDTO getSingle(UUID uuid) throws NotFoundException {
+ final Train train = getByIdOrThrow(uuid);
+ return trainMapper.toDTO(train);
+ }
+
+ public List getAll(String query) {
+ return trainRepository
+ .findByTitleContainingIgnoreCase(query)
+ .stream()
+ .map(trainMapper::toSimpleDTO)
+ .toList();
+ }
+
+ public List getSuitableStations(UUID uuid) throws NotFoundException {
+ final Train train = getByIdOrThrow(uuid);
+ final Set suitableStations = new HashSet<>();
+ train.getTypes().stream().map(TrainType::getStations).forEach(suitableStations::addAll);
+ return suitableStations.stream().map(stationMapper::toSimpleDTO).toList();
+ }
+
+ @Transactional
+ public TrainDTO update(UUID uuid, TrainUpdateDTO dto) throws NotFoundException {
+ final Train train = getByIdOrThrow(uuid);
+ final List trainTypes =
+ trainTypeService.getTrainTypes(dto.getTrainTypeUuids());
+ Train newTrain =
+ trainRepository.saveAndFlush(trainMapper.fromUpdateDTO(dto, train, trainTypes));
+ if (dto.getFetch()) {
+ newTrain = updateFetch(newTrain);
+ }
+ return trainMapper.toDTO(newTrain);
+ }
+
+ @Transactional
+ public TrainDTO softDelete(UUID uuid) throws NotFoundException {
+ final Train train = getByIdOrThrow(uuid);
+ train.setSoftDeleted(true);
+ return trainMapper.toDTO(trainRepository.saveAndFlush(train));
+ }
+
+ @Transactional
+ public TrainDTO create(TrainCreateDTO dto) {
+ final List trainTypes =
+ trainTypeService.getTrainTypes(dto.getTrainTypeUuids());
+ Train newTrain = trainRepository
+ .saveAndFlush(trainMapper.fromCreateDTO(dto, trainTypes));
+ if (dto.getFetch()) {
+ newTrain = updateFetch(newTrain);
+ }
+ return trainMapper.toDTO(newTrain);
+ }
+
+ private Train updateFetch(Train train) {
+ final Train fetchedTrain = trainGarageIndexer.tryToFetchTrain(train.getUri());
+ if (fetchedTrain != null) {
+ return trainRepository
+ .saveAndFlush(trainMapper.updateFetch(train, fetchedTrain));
+ }
+ return train;
+ }
+}
diff --git a/src/main/java/org/fairdatatrain/trainhandler/service/traingarage/TrainGarageIndexer.java b/src/main/java/org/fairdatatrain/trainhandler/service/traingarage/TrainGarageIndexer.java
new file mode 100644
index 0000000..376548f
--- /dev/null
+++ b/src/main/java/org/fairdatatrain/trainhandler/service/traingarage/TrainGarageIndexer.java
@@ -0,0 +1,241 @@
+/**
+ * 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.trainhandler.service.traingarage;
+
+import lombok.RequiredArgsConstructor;
+import lombok.extern.slf4j.Slf4j;
+import nl.dtls.fairdatapoint.vocabulary.FDT;
+import org.eclipse.rdf4j.model.*;
+import org.eclipse.rdf4j.model.vocabulary.DCAT;
+import org.eclipse.rdf4j.model.vocabulary.RDF;
+import org.eclipse.rdf4j.model.vocabulary.RDFS;
+import org.fairdatatrain.trainhandler.data.model.Train;
+import org.fairdatatrain.trainhandler.data.model.TrainGarage;
+import org.fairdatatrain.trainhandler.data.model.TrainType;
+import org.fairdatatrain.trainhandler.data.model.enums.SyncItemStatus;
+import org.fairdatatrain.trainhandler.data.model.enums.SyncServiceStatus;
+import org.fairdatatrain.trainhandler.data.repository.TrainGarageRepository;
+import org.fairdatatrain.trainhandler.data.repository.TrainRepository;
+import org.fairdatatrain.trainhandler.data.repository.TrainTypeRepository;
+import org.fairdatatrain.trainhandler.service.indexing.BaseIndexer;
+import org.springframework.stereotype.Service;
+import org.springframework.transaction.annotation.Transactional;
+
+import java.util.*;
+import java.util.function.Function;
+import java.util.stream.Collectors;
+
+import static java.lang.String.format;
+import static org.fairdatatrain.trainhandler.utils.RdfIOUtils.*;
+import static org.fairdatatrain.trainhandler.utils.RdfUtils.getObjectsBy;
+import static org.fairdatatrain.trainhandler.utils.RdfUtils.getStringObjectBy;
+import static org.fairdatatrain.trainhandler.utils.TimeUtils.now;
+
+@Slf4j
+@Service
+@RequiredArgsConstructor
+public class TrainGarageIndexer {
+
+ private final TrainGarageRepository trainGarageRepository;
+
+ private final TrainTypeRepository trainTypeRepository;
+
+ private final TrainRepository trainRepository;
+
+ private final BaseIndexer baseIndexer;
+
+ @Transactional
+ public void indexGarage(TrainGarage trainGarage) {
+ final Set visitedUris = new HashSet<>();
+ final Queue toVisitUris = new LinkedList<>();
+ final List trains = new ArrayList<>();
+ final List trainTypes = trainTypeRepository.findAll();
+
+ Model model;
+ try {
+ model = baseIndexer.makeRequest(trainGarage.getUri());
+ updateGarage(trainGarage, model);
+ trains.addAll(extractTrains(trainGarage, model, trainTypes));
+ toVisitUris.addAll(baseIndexer.extractChildren(model));
+ }
+ catch (Exception exception) {
+ updateFaultyGarage(trainGarage);
+ }
+
+ while (!toVisitUris.isEmpty()) {
+ final String uri = toVisitUris.poll();
+ log.debug(format("Indexing garage (%s): traversing %s", trainGarage.getUri(), uri));
+ if (visitedUris.contains(uri)) {
+ continue;
+ }
+ else {
+ visitedUris.add(uri);
+ }
+ try {
+ model = baseIndexer.makeRequest(uri);
+ trains.addAll(extractTrains(trainGarage, model, trainTypes));
+ toVisitUris.addAll(baseIndexer.extractChildren(model));
+ }
+ catch (Exception exception) {
+ log.debug(format("Failed to fetch %s (exception: %s)", uri, exception));
+ }
+ }
+
+ updateTrains(trainGarage, trains);
+ }
+
+ public Train tryToFetchTrain(String uri) {
+ final List trainTypes = trainTypeRepository.findAll();
+ try {
+ final Model model = baseIndexer.makeRequest(uri);
+ final List trains = extractTrains(null, model, trainTypes);
+ if (!trains.isEmpty()) {
+ return trains.get(0);
+ }
+ }
+ catch (Exception exception) {
+ log.debug(format("Skipping %s (exception: %s)", uri, exception));
+ }
+ return null;
+ }
+
+ private void updateGarage(TrainGarage trainGarage, Model model) {
+ trainGarage.setMetadata("");
+ trainGarage.setLastContactAt(now());
+ trainGarage.setStatus(SyncServiceStatus.SYNCED);
+ trainGarageRepository.saveAndFlush(trainGarage);
+ }
+
+ private void updateFaultyGarage(TrainGarage trainGarage) {
+ trainGarage.setStatus(SyncServiceStatus.UNREACHABLE);
+ trainGarageRepository.saveAndFlush(trainGarage);
+ }
+
+ private void updateTrains(TrainGarage trainGarage, List trains) {
+ final List trainsToSave = new ArrayList<>();
+ final Map existingTrains = trainGarage
+ .getTrains()
+ .stream()
+ .collect(Collectors.toMap(Train::getUri, Function.identity()));
+ final Map currentTrains = trains
+ .stream()
+ .collect(Collectors.toMap(Train::getUri, Function.identity()));
+
+ currentTrains.forEach((uri, train) -> {
+ if (existingTrains.containsKey(uri)) {
+ updateTrain(existingTrains.get(uri), train);
+ }
+ else {
+ train.setGarage(trainGarage);
+ trainGarage.getTrains().add(train);
+ trainsToSave.add(train);
+ }
+ });
+ existingTrains.forEach((uri, train) -> {
+ if (!currentTrains.containsKey(uri)) {
+ deprecateTrain(train);
+ }
+ });
+
+ trainRepository.saveAllAndFlush(trainsToSave);
+ }
+
+ private void deprecateTrain(Train existingTrain) {
+ existingTrain.setStatus(SyncItemStatus.DELETED);
+ existingTrain.setUpdatedAt(now());
+ }
+
+ private void updateTrain(Train existingTrain, Train newTrain) {
+ existingTrain.setTitle(newTrain.getTitle());
+ existingTrain.setDescription(newTrain.getDescription());
+ existingTrain.setKeywords(newTrain.getKeywords());
+ existingTrain.setTypes(newTrain.getTypes());
+ existingTrain.setMetadata(newTrain.getMetadata());
+ existingTrain.setLastContactAt(newTrain.getLastContactAt());
+ existingTrain.setUpdatedAt(now());
+ }
+
+ private List extractTrains(
+ TrainGarage trainGarage,
+ Model model,
+ List trainTypes
+ ) {
+ return model
+ .filter(null, RDF.TYPE, FDT.TRAIN)
+ .parallelStream()
+ .map(stmt -> extractTrain(trainGarage, stmt.getSubject(), model, trainTypes))
+ .filter(Objects::nonNull)
+ .toList();
+ }
+
+ private Train extractTrain(
+ TrainGarage trainGarage,
+ Resource resource,
+ Model model,
+ List trainTypes
+ ) {
+ log.info(format("Train found: %s", resource));
+ final String title = getStringObjectBy(model, resource, RDFS.LABEL);
+ final String description = getStringObjectBy(model, resource, RDFS.COMMENT);
+ final List keywords = getObjectsBy(model, resource, DCAT.KEYWORD)
+ .stream()
+ .map(Value::stringValue)
+ .toList();
+ final Set trainTypeUris = getObjectsBy(model, resource, FDT.HASTRAINTYPE)
+ .stream()
+ .map(Value::stringValue)
+ .collect(Collectors.toSet());
+
+ if (title == null || trainTypeUris.isEmpty()) {
+ log.warn(format("Skipping train %s (missing required information)", resource));
+ return null;
+ }
+
+ final List matchingTrainTypes = trainTypes
+ .stream()
+ .filter(trainType -> trainTypeUris.contains(trainType.getUri()))
+ .toList();
+
+ if (matchingTrainTypes.isEmpty()) {
+ log.warn(format("No matching train type found: %s", trainTypeUris));
+ return null;
+ }
+
+ return Train
+ .builder()
+ .uuid(UUID.randomUUID())
+ .uri(resource.stringValue())
+ .title(title)
+ .description(description == null ? "" : description)
+ .keywords(String.join(",", keywords))
+ .metadata(write(model))
+ .types(matchingTrainTypes)
+ .garage(trainGarage)
+ .status(SyncItemStatus.SYNCED)
+ .lastContactAt(now())
+ .createdAt(now())
+ .updatedAt(now())
+ .softDeleted(false)
+ .build();
+ }
+}
diff --git a/src/main/java/org/fairdatatrain/trainhandler/service/traingarage/TrainGarageMapper.java b/src/main/java/org/fairdatatrain/trainhandler/service/traingarage/TrainGarageMapper.java
new file mode 100644
index 0000000..80e1fd3
--- /dev/null
+++ b/src/main/java/org/fairdatatrain/trainhandler/service/traingarage/TrainGarageMapper.java
@@ -0,0 +1,90 @@
+/**
+ * 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.trainhandler.service.traingarage;
+
+import org.fairdatatrain.trainhandler.api.dto.traingarage.TrainGarageChangeDTO;
+import org.fairdatatrain.trainhandler.api.dto.traingarage.TrainGarageDTO;
+import org.fairdatatrain.trainhandler.api.dto.traingarage.TrainGarageSimpleDTO;
+import org.fairdatatrain.trainhandler.data.model.TrainGarage;
+import org.fairdatatrain.trainhandler.data.model.enums.SyncServiceStatus;
+import org.springframework.stereotype.Component;
+
+import java.sql.Timestamp;
+import java.time.Instant;
+import java.util.Optional;
+
+@Component
+public class TrainGarageMapper {
+ public TrainGarageDTO toDTO(TrainGarage trainGarage) {
+ return TrainGarageDTO.builder()
+ .uuid(trainGarage.getUuid())
+ .uri(trainGarage.getUri())
+ .displayName(trainGarage.getDisplayName())
+ .note(trainGarage.getNote())
+ .metadata(trainGarage.getMetadata())
+ .status(trainGarage.getStatus())
+ .deletable(trainGarage.isDeletable())
+ .lastContactAt(
+ Optional.ofNullable(trainGarage.getLastContactAt())
+ .map(Timestamp::toInstant)
+ .map(Instant::toString)
+ .orElse(null))
+ .createdAt(trainGarage.getCreatedAt().toInstant().toString())
+ .updatedAt(trainGarage.getCreatedAt().toInstant().toString())
+ .build();
+ }
+
+ public TrainGarage fromCreateDTO(TrainGarageChangeDTO reqDto) {
+ final Timestamp now = Timestamp.from(Instant.now());
+ return TrainGarage.builder()
+ .uri(reqDto.getUri())
+ .displayName(reqDto.getDisplayName())
+ .note(reqDto.getNote())
+ .status(SyncServiceStatus.SYNCING)
+ .metadata(null)
+ .lastContactAt(null)
+ .createdAt(now)
+ .updatedAt(now)
+ .build();
+ }
+
+ public TrainGarage fromUpdateDTO(
+ TrainGarageChangeDTO reqDto,
+ TrainGarage trainGarage
+ ) {
+ final Timestamp now = Timestamp.from(Instant.now());
+ trainGarage.setUri(reqDto.getUri());
+ trainGarage.setDisplayName(reqDto.getDisplayName());
+ trainGarage.setNote(reqDto.getNote());
+ trainGarage.setUpdatedAt(now);
+ return trainGarage;
+ }
+
+ public TrainGarageSimpleDTO toSimpleDTO(TrainGarage trainGarage) {
+ return TrainGarageSimpleDTO.builder()
+ .uuid(trainGarage.getUuid())
+ .uri(trainGarage.getUri())
+ .displayName(trainGarage.getDisplayName())
+ .build();
+ }
+}
diff --git a/src/main/java/org/fairdatatrain/trainhandler/service/traingarage/TrainGarageService.java b/src/main/java/org/fairdatatrain/trainhandler/service/traingarage/TrainGarageService.java
new file mode 100644
index 0000000..9349e96
--- /dev/null
+++ b/src/main/java/org/fairdatatrain/trainhandler/service/traingarage/TrainGarageService.java
@@ -0,0 +1,109 @@
+/**
+ * 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.trainhandler.service.traingarage;
+
+import lombok.RequiredArgsConstructor;
+import org.fairdatatrain.trainhandler.api.dto.traingarage.TrainGarageChangeDTO;
+import org.fairdatatrain.trainhandler.api.dto.traingarage.TrainGarageDTO;
+import org.fairdatatrain.trainhandler.data.model.TrainGarage;
+import org.fairdatatrain.trainhandler.data.model.enums.SyncServiceStatus;
+import org.fairdatatrain.trainhandler.data.repository.TrainGarageRepository;
+import org.fairdatatrain.trainhandler.exception.NotFoundException;
+import org.springframework.data.domain.Page;
+import org.springframework.data.domain.Pageable;
+import org.springframework.scheduling.annotation.Async;
+import org.springframework.stereotype.Service;
+import org.springframework.transaction.annotation.Transactional;
+
+import java.util.UUID;
+
+@Service
+@RequiredArgsConstructor
+public class TrainGarageService {
+
+ private static final String ENTITY_NAME = "TrainGarage";
+
+ private final TrainGarageRepository trainGarageRepository;
+
+ private final TrainGarageMapper trainGarageMapper;
+
+ private final TrainGarageIndexer trainGarageIndexer;
+
+ private TrainGarage getByIdOrThrow(UUID uuid) throws NotFoundException {
+ return trainGarageRepository
+ .findById(uuid)
+ .orElseThrow(() -> new NotFoundException(ENTITY_NAME, uuid));
+ }
+
+ public void delete(UUID uuid) throws NotFoundException {
+ final TrainGarage trainGarage = getByIdOrThrow(uuid);
+ trainGarageRepository.delete(trainGarage);
+ }
+
+ public Page getPaged(String query, Pageable pageable) {
+ if (query.isBlank()) {
+ return trainGarageRepository.findAll(pageable).map(trainGarageMapper::toDTO);
+ }
+ return trainGarageRepository
+ .findByDisplayNameContainingIgnoreCase(query, pageable)
+ .map(trainGarageMapper::toDTO);
+ }
+
+ public TrainGarageDTO getSingle(UUID uuid) throws NotFoundException {
+ final TrainGarage trainGarage = getByIdOrThrow(uuid);
+ return trainGarageMapper.toDTO(trainGarage);
+ }
+
+ @Transactional
+ public TrainGarageDTO create(TrainGarageChangeDTO reqDto) {
+ // TODO: validate?
+ final TrainGarage newTrainGarage =
+ trainGarageRepository.saveAndFlush(trainGarageMapper.fromCreateDTO(reqDto));
+ return trainGarageMapper.toDTO(newTrainGarage);
+ }
+
+ @Transactional
+ public TrainGarageDTO update(UUID uuid, TrainGarageChangeDTO reqDto)
+ throws NotFoundException {
+ // TODO: validate?
+ final TrainGarage trainGarage = getByIdOrThrow(uuid);
+ final TrainGarage updatedTrainGarage =
+ trainGarageRepository.saveAndFlush(
+ trainGarageMapper.fromUpdateDTO(reqDto, trainGarage));
+ return trainGarageMapper.toDTO(updatedTrainGarage);
+ }
+
+ @Transactional
+ public void reindex(UUID uuid) throws NotFoundException {
+ final TrainGarage trainGarage = getByIdOrThrow(uuid);
+ trainGarage.setStatus(SyncServiceStatus.SYNCING);
+ final TrainGarage updatedTrainGarage =
+ trainGarageRepository.saveAndFlush(trainGarage);
+ triggerAsyncIndexing(updatedTrainGarage);
+ }
+
+ @Async
+ protected void triggerAsyncIndexing(TrainGarage trainGarage) {
+ trainGarageIndexer.indexGarage(trainGarage);
+ }
+}
diff --git a/src/main/java/org/fairdatatrain/trainhandler/service/traintype/TrainTypeMapper.java b/src/main/java/org/fairdatatrain/trainhandler/service/traintype/TrainTypeMapper.java
new file mode 100644
index 0000000..fc04153
--- /dev/null
+++ b/src/main/java/org/fairdatatrain/trainhandler/service/traintype/TrainTypeMapper.java
@@ -0,0 +1,76 @@
+/**
+ * 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.trainhandler.service.traintype;
+
+import org.fairdatatrain.trainhandler.api.dto.traintype.TrainTypeChangeDTO;
+import org.fairdatatrain.trainhandler.api.dto.traintype.TrainTypeDTO;
+import org.fairdatatrain.trainhandler.api.dto.traintype.TrainTypeSimpleDTO;
+import org.fairdatatrain.trainhandler.data.model.TrainType;
+import org.springframework.stereotype.Component;
+
+import java.sql.Timestamp;
+import java.time.Instant;
+import java.util.UUID;
+
+@Component
+public class TrainTypeMapper {
+ public TrainTypeDTO toDTO(TrainType trainType) {
+ return TrainTypeDTO.builder()
+ .uuid(trainType.getUuid())
+ .title(trainType.getTitle())
+ .uri(trainType.getUri())
+ .note(trainType.getNote())
+ .createdAt(trainType.getCreatedAt().toInstant().toString())
+ .updatedAt(trainType.getUpdatedAt().toInstant().toString())
+ .build();
+ }
+
+ public TrainType fromCreateDTO(TrainTypeChangeDTO reqDto) {
+ final Timestamp now = Timestamp.from(Instant.now());
+ return TrainType.builder()
+ .uuid(UUID.randomUUID())
+ .title(reqDto.getTitle())
+ .uri(reqDto.getUri())
+ .note(reqDto.getNote())
+ .createdAt(now)
+ .updatedAt(now)
+ .build();
+ }
+
+ public TrainType fromUpdateDTO(TrainTypeChangeDTO reqDto, TrainType trainType) {
+ final Timestamp now = Timestamp.from(Instant.now());
+ trainType.setTitle(reqDto.getTitle());
+ trainType.setUri(reqDto.getUri());
+ trainType.setNote(reqDto.getNote());
+ trainType.setUpdatedAt(now);
+ return trainType;
+ }
+
+ public TrainTypeSimpleDTO toSimpleDTO(TrainType trainType) {
+ return TrainTypeSimpleDTO.builder()
+ .uuid(trainType.getUuid())
+ .title(trainType.getTitle())
+ .uri(trainType.getUri())
+ .build();
+ }
+}
diff --git a/src/main/java/org/fairdatatrain/trainhandler/service/traintype/TrainTypeService.java b/src/main/java/org/fairdatatrain/trainhandler/service/traintype/TrainTypeService.java
new file mode 100644
index 0000000..4fcc2e6
--- /dev/null
+++ b/src/main/java/org/fairdatatrain/trainhandler/service/traintype/TrainTypeService.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.trainhandler.service.traintype;
+
+import lombok.RequiredArgsConstructor;
+import org.fairdatatrain.trainhandler.api.dto.traintype.TrainTypeChangeDTO;
+import org.fairdatatrain.trainhandler.api.dto.traintype.TrainTypeDTO;
+import org.fairdatatrain.trainhandler.api.dto.traintype.TrainTypeSimpleDTO;
+import org.fairdatatrain.trainhandler.data.model.TrainType;
+import org.fairdatatrain.trainhandler.data.repository.TrainTypeRepository;
+import org.fairdatatrain.trainhandler.exception.NotFoundException;
+import org.springframework.data.domain.Page;
+import org.springframework.data.domain.Pageable;
+import org.springframework.stereotype.Service;
+import org.springframework.transaction.annotation.Transactional;
+
+import java.util.List;
+import java.util.Objects;
+import java.util.UUID;
+
+@Service
+@RequiredArgsConstructor
+public class TrainTypeService {
+
+ private static final String ENTITY_NAME = "TrainType";
+
+ private final TrainTypeRepository trainTypeRepository;
+
+ private final TrainTypeMapper trainTypeMapper;
+
+ public TrainType getByIdOrThrow(UUID uuid) throws NotFoundException {
+ return trainTypeRepository
+ .findById(uuid)
+ .orElseThrow(() -> new NotFoundException(ENTITY_NAME, uuid));
+ }
+
+ public TrainType getByIdOrNull(UUID uuid) {
+ return trainTypeRepository
+ .findById(uuid)
+ .orElse(null);
+ }
+
+ public List getTrainTypes(List uuids) {
+ return uuids
+ .stream()
+ .map(this::getByIdOrNull)
+ .filter(Objects::nonNull)
+ .toList();
+ }
+
+ public void delete(UUID uuid) throws NotFoundException {
+ final TrainType trainType = getByIdOrThrow(uuid);
+ trainTypeRepository.delete(trainType);
+ }
+
+ public Page getPaged(String query, Pageable pageable) {
+ if (query.isBlank()) {
+ return trainTypeRepository.findAll(pageable).map(trainTypeMapper::toDTO);
+ }
+ return trainTypeRepository
+ .findByTitleContainingIgnoreCase(query, pageable)
+ .map(trainTypeMapper::toDTO);
+ }
+
+ public TrainTypeDTO getSingle(UUID uuid) throws NotFoundException {
+ final TrainType trainType = getByIdOrThrow(uuid);
+ return trainTypeMapper.toDTO(trainType);
+ }
+
+ @Transactional
+ public TrainTypeDTO create(TrainTypeChangeDTO reqDto) {
+ // TODO: validate?
+ final TrainType newTrainType =
+ trainTypeRepository.saveAndFlush(trainTypeMapper.fromCreateDTO(reqDto));
+ return trainTypeMapper.toDTO(newTrainType);
+ }
+
+ @Transactional
+ public TrainTypeDTO update(UUID uuid, TrainTypeChangeDTO reqDto) throws NotFoundException {
+ // TODO: validate?
+ final TrainType trainType = getByIdOrThrow(uuid);
+ final TrainType updatedTrainType =
+ trainTypeRepository.saveAndFlush(trainTypeMapper.fromUpdateDTO(reqDto, trainType));
+ return trainTypeMapper.toDTO(updatedTrainType);
+ }
+
+ public List getAll() {
+ return trainTypeRepository
+ .findAll()
+ .stream()
+ .map(trainTypeMapper::toSimpleDTO)
+ .toList();
+ }
+}
diff --git a/src/main/java/org/fairdatatrain/trainhandler/utils/CompareUtils.java b/src/main/java/org/fairdatatrain/trainhandler/utils/CompareUtils.java
new file mode 100644
index 0000000..fd97189
--- /dev/null
+++ b/src/main/java/org/fairdatatrain/trainhandler/utils/CompareUtils.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.trainhandler.utils;
+
+import java.util.Collection;
+
+public class CompareUtils {
+
+ public static boolean compareListContents(Collection listA, Collection listB) {
+ if (listA.size() != listB.size()) {
+ return false;
+ }
+ return listA.retainAll(listB);
+ }
+}
diff --git a/src/main/java/org/fairdatatrain/trainhandler/utils/HashUtils.java b/src/main/java/org/fairdatatrain/trainhandler/utils/HashUtils.java
new file mode 100644
index 0000000..6e89aee
--- /dev/null
+++ b/src/main/java/org/fairdatatrain/trainhandler/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.trainhandler.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/trainhandler/utils/RandomUtils.java b/src/main/java/org/fairdatatrain/trainhandler/utils/RandomUtils.java
new file mode 100644
index 0000000..68b85a2
--- /dev/null
+++ b/src/main/java/org/fairdatatrain/trainhandler/utils/RandomUtils.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.trainhandler.utils;
+
+import java.util.UUID;
+
+public class RandomUtils {
+ public static String randomSecret() {
+ return UUID.randomUUID().toString();
+ }
+}
diff --git a/src/main/java/org/fairdatatrain/trainhandler/utils/RdfIOUtils.java b/src/main/java/org/fairdatatrain/trainhandler/utils/RdfIOUtils.java
new file mode 100644
index 0000000..5dacaae
--- /dev/null
+++ b/src/main/java/org/fairdatatrain/trainhandler/utils/RdfIOUtils.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.trainhandler.utils;
+
+import org.eclipse.rdf4j.model.Model;
+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;
+
+public class RdfIOUtils {
+
+ private static final WriterConfig WRITER_CONFIG = new WriterConfig();
+
+ static {
+ WRITER_CONFIG.set(BasicWriterSettings.INLINE_BLANK_NODES, true);
+ }
+
+ 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(XMLSchema.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/trainhandler/utils/RdfUtils.java b/src/main/java/org/fairdatatrain/trainhandler/utils/RdfUtils.java
new file mode 100644
index 0000000..1e3a91f
--- /dev/null
+++ b/src/main/java/org/fairdatatrain/trainhandler/utils/RdfUtils.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.trainhandler.utils;
+
+import org.eclipse.rdf4j.model.*;
+import org.eclipse.rdf4j.model.util.Models;
+
+import java.util.List;
+
+import static org.fairdatatrain.trainhandler.utils.ValueFactoryUtils.i;
+
+public class RdfUtils {
+
+ 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);
+ }
+}
diff --git a/src/main/java/org/fairdatatrain/trainhandler/utils/TimeUtils.java b/src/main/java/org/fairdatatrain/trainhandler/utils/TimeUtils.java
new file mode 100644
index 0000000..2dc49d3
--- /dev/null
+++ b/src/main/java/org/fairdatatrain/trainhandler/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.trainhandler.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/java/org/fairdatatrain/trainhandler/utils/ValueFactoryUtils.java b/src/main/java/org/fairdatatrain/trainhandler/utils/ValueFactoryUtils.java
new file mode 100644
index 0000000..aa5d059
--- /dev/null
+++ b/src/main/java/org/fairdatatrain/trainhandler/utils/ValueFactoryUtils.java
@@ -0,0 +1,91 @@
+/**
+ * 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.trainhandler.utils;
+
+import org.eclipse.rdf4j.model.*;
+import org.eclipse.rdf4j.model.impl.SimpleValueFactory;
+import org.eclipse.rdf4j.model.util.Values;
+
+import java.util.Optional;
+
+public class ValueFactoryUtils {
+
+ private static final String PREFIX_SEP = ":";
+
+ private static final ValueFactory VF = SimpleValueFactory.getInstance();
+
+ public static IRI i(String iri) {
+ if (iri == null) {
+ return null;
+ }
+ return Values.iri(iri);
+ }
+
+ public static IRI i(Optional optionalIri) {
+ if (optionalIri.isEmpty()) {
+ return null;
+ }
+ return i(optionalIri.get());
+ }
+
+ 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 IRI i(Value value) {
+ if (value == null) {
+ return null;
+ }
+ return i(value.stringValue());
+ }
+
+ public static Statement s(Resource subject, IRI predicate, Value object) {
+ return VF.createStatement(subject, predicate, object);
+ }
+
+ public static Statement s(Resource subject, IRI predicate, Value object, Resource context) {
+ return VF.createStatement(subject, predicate, object, context);
+ }
+}
diff --git a/src/main/resources/META-INF/additional-spring-configuration-metadata.json b/src/main/resources/META-INF/additional-spring-configuration-metadata.json
new file mode 100644
index 0000000..b08f455
--- /dev/null
+++ b/src/main/resources/META-INF/additional-spring-configuration-metadata.json
@@ -0,0 +1,24 @@
+{
+ "properties": [
+ {
+ "name": "dispatcher.polling.timeout",
+ "type": "java.lang.String",
+ "description": "HTTP Long polling timeout (ISO-8601 duration)"
+ },
+ {
+ "name": "dispatcher.dispatch.root",
+ "type": "java.lang.String",
+ "description": "Root URL for composing callback URLs"
+ },
+ {
+ "name": "dispatcher.dispatch.initDelay",
+ "type": "java.lang.String",
+ "description": "Initial delay for periodic dispatching task (ISO-8601 duration)"
+ },
+ {
+ "name": "dispatcher.dispatch.interval",
+ "type": "java.lang.String",
+ "description": "Interval for periodic dispatching task (ISO-8601 duration)"
+ }
+ ]
+}
\ No newline at end of file
diff --git a/src/main/resources/application-ci.yml b/src/main/resources/application-ci.yml
new file mode 100644
index 0000000..604bf29
--- /dev/null
+++ b/src/main/resources/application-ci.yml
@@ -0,0 +1,5 @@
+spring:
+ datasource:
+ url: jdbc:postgresql://localhost/train-handler-test
+ username: train-handler
+ password: password
diff --git a/src/main/resources/application-dev.yml b/src/main/resources/application-dev.yml
new file mode 100644
index 0000000..0556729
--- /dev/null
+++ b/src/main/resources/application-dev.yml
@@ -0,0 +1,23 @@
+spring:
+ datasource:
+ url: jdbc:postgresql://localhost/train_handler
+ username: postgres
+ password: password
+ flyway:
+ locations: classpath:db/migration,classpath:dev/db/migration
+
+keycloak:
+ enabled: false
+ realm: myrealm
+ auth-server-url: http://localhost:8000
+ resource: myapi
+ public-client: true
+ bearer-only: true
+
+dispatcher:
+ dispatch:
+ root: http://localhost:8080
+
+logging:
+ level:
+ com.zaxxer.hikari: TRACE
diff --git a/src/main/resources/application-test.yml b/src/main/resources/application-test.yml
new file mode 100644
index 0000000..df22c71
--- /dev/null
+++ b/src/main/resources/application-test.yml
@@ -0,0 +1,8 @@
+spring:
+ datasource:
+ url: jdbc:postgresql://localhost/train_handler_test
+ username: postgres
+ password: password
+
+keycloak:
+ enabled: false
diff --git a/src/main/resources/application.yml b/src/main/resources/application.yml
new file mode 100644
index 0000000..d3e5126
--- /dev/null
+++ b/src/main/resources/application.yml
@@ -0,0 +1,51 @@
+dispatcher:
+ polling:
+ timeout: ${FDT_POLLING_TIMEOUT:PT2M}
+ dispatch:
+ root: ${FDT_DISPATCH_ROOT:http://localhost:8080}
+ initDelay: ${FDT_DISPATCH_INIT_DELAY:PT30S}
+ interval: ${FDT_DISPATCH_INTERVAL:PT30S}
+
+spring:
+ main:
+ banner-mode: off
+ datasource:
+ url: jdbc:postgresql://${FDT_POSTGRES_HOST:postgres}:${FDT_POSTGRES_PORT:5432}/${FDT_POSTGRES_DB:train_handler}
+ username: ${FDT_POSTGRES_USERNAME:postgres}
+ password: ${FDT_POSTGRES_PASSWORD:password}
+ hikari:
+ pool-name: PGHikariPool
+ max-pool-size: 10
+ min-idle: 5
+ flyway:
+ locations: classpath:db/migration
+ jpa:
+ properties:
+ hibernate:
+ ddl-auto: validate
+ dialect: org.hibernate.dialect.PostgreSQLDialect
+
+keycloak:
+ enabled: ${FDT_KEYCLOAK_ENABLED:false}
+ realm: ${FDT_KEYCLOAK_REALM:myrealm}
+ auth-server-url: ${FDT_KEYCLOAK_URL:http://localhost:8000}
+ resource: ${FDT_KEYCLOAK_RESOURCE:myapi}
+ public-client: true
+ bearer-only: true
+
+springdoc:
+ swagger-ui:
+ disable-swagger-default-url: true
+ operationsSorter: alpha
+
+management:
+ health:
+ solr:
+ enabled: false
+ info:
+ defaults:
+ enabled: false
+ endpoints:
+ web:
+ exposure:
+ include: health, info
diff --git a/src/main/resources/db/migration/.gitkeep b/src/main/resources/db/migration/.gitkeep
new file mode 100644
index 0000000..e69de29
diff --git a/src/main/resources/db/migration/V0001__directory-and-garage.sql b/src/main/resources/db/migration/V0001__directory-and-garage.sql
new file mode 100644
index 0000000..311a55e
--- /dev/null
+++ b/src/main/resources/db/migration/V0001__directory-and-garage.sql
@@ -0,0 +1,50 @@
+--
+-- 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.
+--
+
+CREATE TABLE IF NOT EXISTS station_directory
+(
+ uuid UUID NOT NULL
+ CONSTRAINT station_directory_pk PRIMARY KEY,
+ uri TEXT NOT NULL,
+ display_name VARCHAR(255) NOT NULL,
+ note TEXT NOT NULL,
+ metadata TEXT,
+ status VARCHAR(50),
+ last_contact TIMESTAMP,
+ created_at TIMESTAMP NOT NULL,
+ updated_at TIMESTAMP NOT NULL
+);
+
+CREATE TABLE IF NOT EXISTS train_garage
+(
+ uuid UUID NOT NULL
+ CONSTRAINT train_garage_pk PRIMARY KEY,
+ uri TEXT NOT NULL,
+ display_name VARCHAR(255) NOT NULL,
+ note TEXT NOT NULL,
+ metadata TEXT,
+ status VARCHAR(50),
+ last_contact TIMESTAMP,
+ created_at TIMESTAMP NOT NULL,
+ updated_at TIMESTAMP NOT NULL
+);
diff --git a/src/main/resources/db/migration/V0002.0__trains-stations.sql b/src/main/resources/db/migration/V0002.0__trains-stations.sql
new file mode 100644
index 0000000..c83f2b1
--- /dev/null
+++ b/src/main/resources/db/migration/V0002.0__trains-stations.sql
@@ -0,0 +1,108 @@
+--
+-- 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.
+--
+
+ALTER TABLE station_directory
+ RENAME COLUMN last_contact TO last_contact_at;
+ALTER TABLE train_garage
+ RENAME COLUMN last_contact TO last_contact_at;
+
+CREATE TABLE IF NOT EXISTS train_type
+(
+ uuid UUID NOT NULL
+ CONSTRAINT train_type_pk PRIMARY KEY,
+ uri TEXT NOT NULL,
+ title TEXT NOT NULL,
+ note TEXT NOT NULL,
+ created_at TIMESTAMP NOT NULL,
+ updated_at TIMESTAMP NOT NULL
+);
+
+CREATE TABLE IF NOT EXISTS train
+(
+ uuid UUID NOT NULL
+ CONSTRAINT train_pk PRIMARY KEY,
+ uri TEXT NOT NULL,
+ title TEXT NOT NULL,
+ description TEXT NOT NULL,
+ keywords TEXT,
+ metadata TEXT,
+ status VARCHAR(50),
+ last_contact_at TIMESTAMP,
+ train_garage_id UUID NOT NULL,
+ created_at TIMESTAMP NOT NULL,
+ updated_at TIMESTAMP NOT NULL
+);
+
+ALTER TABLE train
+ ADD CONSTRAINT train_train_garage_fk FOREIGN KEY (train_garage_id) REFERENCES train_garage (uuid);
+
+CREATE TABLE IF NOT EXISTS station
+(
+ uuid UUID NOT NULL
+ CONSTRAINT station_pk PRIMARY KEY,
+ uri TEXT NOT NULL,
+ title TEXT NOT NULL,
+ description TEXT NOT NULL,
+ keywords TEXT,
+ metadata TEXT,
+ status VARCHAR(50),
+ last_contact_at TIMESTAMP,
+ station_directory_id UUID NOT NULL,
+ created_at TIMESTAMP NOT NULL,
+ updated_at TIMESTAMP NOT NULL
+);
+
+ALTER TABLE station
+ ADD CONSTRAINT station_station_directory_fk FOREIGN KEY (station_directory_id) REFERENCES station_directory (uuid);
+
+
+CREATE TABLE IF NOT EXISTS train_train_type
+(
+ train_type_id uuid NOT NULL,
+ train_id uuid NOT NULL
+);
+
+ALTER TABLE ONLY train_train_type
+ ADD CONSTRAINT train_train_type_pk PRIMARY KEY (train_type_id, train_id);
+
+ALTER TABLE ONLY train_train_type
+ ADD CONSTRAINT train_train_type_train_type_fk FOREIGN KEY (train_type_id) REFERENCES train_type (uuid);
+
+ALTER TABLE ONLY train_train_type
+ ADD CONSTRAINT train_train_type_train_fk FOREIGN KEY (train_id) REFERENCES train (uuid);
+
+
+CREATE TABLE IF NOT EXISTS station_train_type
+(
+ train_type_id uuid NOT NULL,
+ station_id uuid NOT NULL
+);
+
+ALTER TABLE ONLY station_train_type
+ ADD CONSTRAINT station_train_type_pk PRIMARY KEY (train_type_id, station_id);
+
+ALTER TABLE ONLY station_train_type
+ ADD CONSTRAINT station_train_type_train_type_fk FOREIGN KEY (train_type_id) REFERENCES train_type (uuid);
+
+ALTER TABLE ONLY station_train_type
+ ADD CONSTRAINT station_train_type_station_fk FOREIGN KEY (station_id) REFERENCES station (uuid);
diff --git a/src/main/resources/db/migration/V0003.0__plans-runs.sql b/src/main/resources/db/migration/V0003.0__plans-runs.sql
new file mode 100644
index 0000000..fb193a0
--- /dev/null
+++ b/src/main/resources/db/migration/V0003.0__plans-runs.sql
@@ -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.
+--
+
+CREATE TABLE IF NOT EXISTS plan
+(
+ uuid UUID NOT NULL
+ CONSTRAINT train_instance_pk PRIMARY KEY,
+ display_name TEXT NOT NULL,
+ note TEXT NOT NULL,
+ train_id UUID NOT NULL,
+ created_at TIMESTAMP NOT NULL,
+ updated_at TIMESTAMP NOT NULL
+);
+
+ALTER TABLE plan
+ ADD CONSTRAINT plan_train_fk FOREIGN KEY (train_id) REFERENCES train (uuid);
+
+CREATE TABLE IF NOT EXISTS plan_target
+(
+ uuid UUID NOT NULL
+ CONSTRAINT plan_target_pk PRIMARY KEY,
+ station_id UUID NOT NULL,
+ plan_id UUID NOT NULL,
+ created_at TIMESTAMP NOT NULL,
+ updated_at TIMESTAMP NOT NULL
+);
+
+ALTER TABLE plan_target
+ ADD CONSTRAINT plan_target_station_fk FOREIGN KEY (station_id) REFERENCES station (uuid);
+
+ALTER TABLE plan_target
+ ADD CONSTRAINT plan_target_plan_fk FOREIGN KEY (plan_id) REFERENCES plan (uuid);
diff --git a/src/main/resources/db/migration/V0003.1__sync-status-enums.sql b/src/main/resources/db/migration/V0003.1__sync-status-enums.sql
new file mode 100644
index 0000000..09e2a94
--- /dev/null
+++ b/src/main/resources/db/migration/V0003.1__sync-status-enums.sql
@@ -0,0 +1,59 @@
+--
+-- 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.
+--
+
+CREATE TYPE sync_service_status AS ENUM (
+ 'SYNCED',
+ 'SYNCING',
+ 'UNREACHABLE',
+ 'INVALID',
+ 'DELETED'
+ );
+
+CREATE CAST (character varying AS sync_service_status) WITH INOUT AS ASSIGNMENT;
+
+ALTER TABLE station_directory
+ DROP COLUMN status;
+ALTER TABLE station_directory
+ ADD COLUMN status sync_service_status NOT NULL DEFAULT 'SYNCED';
+
+ALTER TABLE train_garage
+ DROP COLUMN status;
+ALTER TABLE train_garage
+ ADD COLUMN status sync_service_status NOT NULL DEFAULT 'SYNCED';
+
+CREATE TYPE sync_item_status AS ENUM (
+ 'SYNCED',
+ 'DELETED'
+ );
+
+CREATE CAST (character varying AS sync_item_status) WITH INOUT AS ASSIGNMENT;
+
+ALTER TABLE train
+ DROP COLUMN status;
+ALTER TABLE train
+ ADD COLUMN status sync_item_status NOT NULL DEFAULT 'SYNCED';
+
+ALTER TABLE station
+ DROP COLUMN status;
+ALTER TABLE station
+ ADD COLUMN status sync_item_status NOT NULL DEFAULT 'SYNCED';
diff --git a/src/main/resources/db/migration/V0003.2__runs_jobs_events.sql b/src/main/resources/db/migration/V0003.2__runs_jobs_events.sql
new file mode 100644
index 0000000..b53ad77
--- /dev/null
+++ b/src/main/resources/db/migration/V0003.2__runs_jobs_events.sql
@@ -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.
+--
+
+CREATE TYPE run_status AS ENUM (
+ 'SCHEDULED',
+ 'PREPARED',
+ 'RUNNING',
+ 'FINISHED',
+ 'ABORTING',
+ 'ABORTED',
+ 'ERRORED',
+ 'FAILED'
+ );
+
+CREATE CAST (character varying AS run_status) WITH INOUT AS ASSIGNMENT;
+
+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 job_event_type AS ENUM (
+ 'ARTEFACT',
+ 'INIT',
+ 'INFO'
+ );
+
+CREATE CAST (character varying AS job_event_type) WITH INOUT AS ASSIGNMENT;
+
+CREATE TABLE IF NOT EXISTS run
+(
+ uuid UUID NOT NULL
+ CONSTRAINT run_pk PRIMARY KEY,
+ display_name VARCHAR(255) NOT NULL,
+ note TEXT NOT NULL,
+ status run_status NOT NULL,
+ should_start_at TIMESTAMP,
+ started_at TIMESTAMP,
+ finished_at TIMESTAMP,
+ plan_id UUID NOT NULL,
+ created_at TIMESTAMP NOT NULL,
+ updated_at TIMESTAMP NOT NULL
+);
+
+ALTER TABLE ONLY run
+ ADD CONSTRAINT run_plan_fk FOREIGN KEY (plan_id) REFERENCES plan (uuid);
+
+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,
+ plan_target_id UUID NOT NULL,
+ run_id UUID NOT NULL,
+ created_at TIMESTAMP NOT NULL,
+ updated_at TIMESTAMP NOT NULL
+);
+
+ALTER TABLE ONLY job
+ ADD CONSTRAINT job_plan_target_fk FOREIGN KEY (plan_target_id) REFERENCES plan_target (uuid);
+
+ALTER TABLE ONLY job
+ ADD CONSTRAINT job_run_fk FOREIGN KEY (run_id) REFERENCES run (uuid);
+
+CREATE TABLE IF NOT EXISTS job_event
+(
+ uuid UUID NOT NULL
+ CONSTRAINT job_event_pk PRIMARY KEY,
+ type job_event_type NOT NULL,
+ result_status job_status,
+ occurred_at TIMESTAMP,
+ message TEXT NOT NULL,
+ payload JSON,
+ job_id UUID NOT NULL,
+ 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);
+
+
+
diff --git a/src/main/resources/db/migration/V0003.4__job-artifacts.sql b/src/main/resources/db/migration/V0003.4__job-artifacts.sql
new file mode 100644
index 0000000..876132e
--- /dev/null
+++ b/src/main/resources/db/migration/V0003.4__job-artifacts.sql
@@ -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.
+--
+
+ALTER TABLE job_event
+ DROP COLUMN type;
+
+ALTER TABLE job_event
+ DROP COLUMN payload;
+
+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_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,
+ 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/V0003.6__run-job-version.sql b/src/main/resources/db/migration/V0003.6__run-job-version.sql
new file mode 100644
index 0000000..f0d367e
--- /dev/null
+++ b/src/main/resources/db/migration/V0003.6__run-job-version.sql
@@ -0,0 +1,28 @@
+--
+-- 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.
+--
+
+ALTER TABLE run
+ ADD COLUMN version BIGINT NOT NULL DEFAULT 0;
+
+ALTER TABLE job
+ ADD COLUMN version BIGINT NOT NULL DEFAULT 0;
diff --git a/src/main/resources/db/migration/V0004.0__add-soft-delete.sql b/src/main/resources/db/migration/V0004.0__add-soft-delete.sql
new file mode 100644
index 0000000..8e0ea36
--- /dev/null
+++ b/src/main/resources/db/migration/V0004.0__add-soft-delete.sql
@@ -0,0 +1,28 @@
+--
+-- 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.
+--
+
+ALTER TABLE train
+ ADD COLUMN soft_deleted BOOLEAN NOT NULL DEFAULT FALSE;
+
+ALTER TABLE station
+ ADD COLUMN soft_deleted BOOLEAN NOT NULL DEFAULT FALSE;
diff --git a/src/main/resources/db/migration/V0005.0__default-train-types.sql b/src/main/resources/db/migration/V0005.0__default-train-types.sql
new file mode 100644
index 0000000..e734071
--- /dev/null
+++ b/src/main/resources/db/migration/V0005.0__default-train-types.sql
@@ -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.
+--
+
+-- Default train types based on FDT ontology
+INSERT INTO public.train_type (uuid, uri, title, note, created_at, updated_at)
+VALUES ('8b4acd34-5738-49d4-918f-7d4955ded0bc', 'https://w3id.org/fdt/fdt-o#DockerTrain', 'Docker Train', '',
+ '2023-01-09 10:00:00.000000', '2023-01-09 10:00:00.000000');
+INSERT INTO public.train_type (uuid, uri, title, note, created_at, updated_at)
+VALUES ('f5eef54f-b85c-4109-b26b-18e15c8e0751', 'https://w3id.org/fdt/fdt-o#SPARQLTrain', 'SPARQL Train', '',
+ '2023-01-09 10:00:00.000000', '2023-01-09 10:00:00.000000');
+INSERT INTO public.train_type (uuid, uri, title, note, created_at, updated_at)
+VALUES ('32d36066-dbf6-4ed3-b4da-aad0edd1639a', 'https://w3id.org/fdt/fdt-o#SQLTrain', 'SQL Train', '',
+ '2023-01-09 10:00:00.000000', '2023-01-09 10:00:00.000000');
+INSERT INTO public.train_type (uuid, uri, title, note, created_at, updated_at)
+VALUES ('767a46c1-0bab-4b87-a8df-de5600946a66', 'https://w3id.org/fdt/fdt-o#FHIRTrain', 'FHIR Train', '',
+ '2023-01-09 10:00:00.000000', '2023-01-09 10:00:00.000000');
+INSERT INTO public.train_type (uuid, uri, title, note, created_at, updated_at)
+VALUES ('77aedb36-592d-4577-8732-7d837acc35ba', 'https://w3id.org/fdt/fdt-o#GraphQLTrain', 'GraphQL Train', '',
+ '2023-01-09 10:00:00.000000', '2023-01-09 10:00:00.000000');
+INSERT INTO public.train_type (uuid, uri, title, note, created_at, updated_at)
+VALUES ('4ec48955-484c-4eee-a8bb-fef0633062d6', 'https://w3id.org/fdt/fdt-o#PythonTrain', 'Python Train', '',
+ '2023-01-09 10:00:00.000000', '2023-01-09 10:00:00.000000');
+INSERT INTO public.train_type (uuid, uri, title, note, created_at, updated_at)
+VALUES ('0da459b5-ec93-4cd5-930d-d9fe86be0d2a', 'https://w3id.org/fdt/fdt-o#RESTTrain', 'REST Train', '',
+ '2023-01-09 10:00:00.000000', '2023-01-09 10:00:00.000000');
+INSERT INTO public.train_type (uuid, uri, title, note, created_at, updated_at)
+VALUES ('9f32d058-1791-4356-8c1e-f735026d91b0', 'https://w3id.org/fdt/fdt-o#RTrain', 'R Train', '',
+ '2023-01-09 10:00:00.000000', '2023-01-09 10:00:00.000000');
+INSERT INTO public.train_type (uuid, uri, title, note, created_at, updated_at)
+VALUES ('f1112bb9-d888-470c-91f3-39a8e888a6d1', 'https://w3id.org/fdt/fdt-o#SingularityTrain', 'Singularity Train', '',
+ '2023-01-09 10:00:00.000000', '2023-01-09 10:00:00.000000');
+INSERT INTO public.train_type (uuid, uri, title, note, created_at, updated_at)
+VALUES ('5513164c-e440-455e-8c71-339324780576', 'https://w3id.org/fdt/fdt-o#TriplePatternFragmentTrain',
+ 'Triple Pattern Fragment Train', '', '2023-01-09 10:00:00.000000', '2023-01-09 10:00:00.000000');
+
diff --git a/src/main/resources/db/migration/V0006.0__station-endpoint.sql b/src/main/resources/db/migration/V0006.0__station-endpoint.sql
new file mode 100644
index 0000000..493fc9b
--- /dev/null
+++ b/src/main/resources/db/migration/V0006.0__station-endpoint.sql
@@ -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.
+--
+
+ALTER TABLE station
+ ADD COLUMN endpoint_url TEXT NOT NULL DEFAULT '';
+ALTER TABLE station
+ ADD COLUMN endpoint_description TEXT NOT NULL DEFAULT '';
+
+UPDATE station
+SET endpoint_url = uri
+WHERE station.endpoint_url = '';
\ No newline at end of file
diff --git a/src/main/resources/db/migration/V0007.0__add-public-artifacts.sql b/src/main/resources/db/migration/V0007.0__add-public-artifacts.sql
new file mode 100644
index 0000000..94f3b94
--- /dev/null
+++ b/src/main/resources/db/migration/V0007.0__add-public-artifacts.sql
@@ -0,0 +1,28 @@
+--
+-- 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.
+--
+
+ALTER TABLE plan
+ ADD COLUMN publish_artifacts BOOLEAN NOT NULL DEFAULT FALSE;
+
+ALTER TABLE plan_target
+ ADD COLUMN publish_artifacts BOOLEAN NOT NULL DEFAULT FALSE;
diff --git a/src/main/resources/db/migration/V0007.1__fix-garage-requirement.sql b/src/main/resources/db/migration/V0007.1__fix-garage-requirement.sql
new file mode 100644
index 0000000..b96d178
--- /dev/null
+++ b/src/main/resources/db/migration/V0007.1__fix-garage-requirement.sql
@@ -0,0 +1,28 @@
+--
+-- 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.
+--
+
+ALTER TABLE train
+ ALTER COLUMN train_garage_id DROP NOT NULL;
+
+ALTER TABLE station
+ ALTER COLUMN station_directory_id DROP NOT NULL;
diff --git a/src/main/resources/dev/db/migration/V0002.1__dev_data.sql b/src/main/resources/dev/db/migration/V0002.1__dev_data.sql
new file mode 100644
index 0000000..8bdb338
--- /dev/null
+++ b/src/main/resources/dev/db/migration/V0002.1__dev_data.sql
@@ -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.
+--
+
+-- Station Directories
+INSERT INTO station_directory (uuid, uri, display_name, note, metadata, status, last_contact_at, created_at, updated_at)
+VALUES ('6dfc348a-3ceb-49b2-ac5a-eff83bdf7eae', 'http://example.com/fds/station-directory1',
+ 'Station Directory 1: Health', 'Note for Station Directory 1 which is an example...', '', 'SYNCED', NOW(),
+ NOW(), NOW());
+
+INSERT INTO station_directory (uuid, uri, display_name, note, metadata, status, last_contact_at, created_at, updated_at)
+VALUES ('4475579a-0497-4b4d-babe-56f6b7a38ee7', 'http://example.com/fds/station-directory2',
+ 'Station Directory 2: COVID', 'Note for Station Directory 2 which is an example...', '', 'SYNCED', NOW(), NOW(),
+ NOW());
+
+INSERT INTO station_directory (uuid, uri, display_name, note, metadata, status, last_contact_at, created_at, updated_at)
+VALUES ('1f50ad0e-aa8a-41b8-8934-d498ad0e2635', 'http://example.com/fds/station-directory3',
+ 'Station Directory 3: Random', 'Note for Station Directory 3 which is an example...', '', 'INVALID', NOW(),
+ NOW(), NOW());
+
+-- Train Garages
+INSERT INTO train_garage (uuid, uri, display_name, note, metadata, status, last_contact_at, created_at, updated_at)
+VALUES ('a3bad37e-9333-4963-88ca-7c0b05a61ee8', 'http://example.com/fds/train-garage1', 'Train Garage 1',
+ 'Note for Train Garage 1 which is an example...', '', 'SYNCED', NOW(), NOW(), NOW());
+
+INSERT INTO train_garage (uuid, uri, display_name, note, metadata, status, last_contact_at, created_at, updated_at)
+VALUES ('7c9594d1-d20e-449a-bd7f-393e1f6b87ac', 'http://example.com/fds/train-garage2', 'Train Garage 2',
+ 'Note for Train Garage 2 which is an example...', '', 'INVALID', NOW(), NOW(), NOW());
+
+-- Train Type
+INSERT INTO train_type (uuid, uri, title, note, created_at, updated_at)
+VALUES ('f5eef54f-b85c-4109-b26b-18e15c8e075f', 'https://w3id.org/fdp/fdt-o#SPARQLTrain', 'SPARQL Train',
+ 'My note about the SPARQL Train type...', NOW(), NOW());
+
+INSERT INTO train_type (uuid, uri, title, note, created_at, updated_at)
+VALUES ('8b4acd34-5738-49d4-918f-7d4955ded0bf', 'https://w3id.org/fdp/fdt-o#DockerTrain', 'Docker Train',
+ 'My note about the Docker Train type...', NOW(), NOW());
+
+INSERT INTO train_type (uuid, uri, title, note, created_at, updated_at)
+VALUES ('32d36066-dbf6-4ed3-b4da-aad0edd1639f', 'https://w3id.org/fdp/fdt-o#SQLTrain', 'SQL Train',
+ 'My note about the SQL Train type...', NOW(), NOW());
+
+-- Stations
+INSERT INTO station (uuid, uri, title, description, keywords, metadata, status, last_contact_at, station_directory_id,
+ created_at, updated_at)
+VALUES ('9b2f99bb-6ca4-4326-82c1-bba34fb39abc', 'http://example.com/fds/station1',
+ 'Station 1: Public Health Data Station', 'Description for station 1 ...', 'Health,SPARQL,SQL,Query', '',
+ 'SYNCED', NOW(), '6dfc348a-3ceb-49b2-ac5a-eff83bdf7eae', NOW(), NOW());
+INSERT INTO station_train_type (train_type_id, station_id)
+VALUES ('f5eef54f-b85c-4109-b26b-18e15c8e075f', '9b2f99bb-6ca4-4326-82c1-bba34fb39abc');
+INSERT INTO station_train_type (train_type_id, station_id)
+VALUES ('32d36066-dbf6-4ed3-b4da-aad0edd1639f', '9b2f99bb-6ca4-4326-82c1-bba34fb39abc');
+
+INSERT INTO station (uuid, uri, title, description, keywords, metadata, status, last_contact_at, station_directory_id,
+ created_at, updated_at)
+VALUES ('a4d6cf81-1e7a-4666-88e7-26fbdd992674', 'http://example.com/fds/station2',
+ 'Station 2: Public COVID Data Station', 'Description for station 2 ...', 'Health,COVID,SPARQL', '', 'SYNCED',
+ NOW(), '6dfc348a-3ceb-49b2-ac5a-eff83bdf7eae', NOW(), NOW());
+INSERT INTO station_train_type (train_type_id, station_id)
+VALUES ('f5eef54f-b85c-4109-b26b-18e15c8e075f', 'a4d6cf81-1e7a-4666-88e7-26fbdd992674');
+
+INSERT INTO station (uuid, uri, title, description, keywords, metadata, status, last_contact_at, station_directory_id,
+ created_at, updated_at)
+VALUES ('f41417ab-ef19-408f-ba69-5d3a1bc42e23', 'http://example.com/fds/station3',
+ 'Station 3: Another COVID Data Station', 'Description for station 3 ...', 'Health,COVID,SPARQL,Docker', '',
+ 'SYNCED', NOW(), '4475579a-0497-4b4d-babe-56f6b7a38ee7', NOW(), NOW());
+INSERT INTO station_train_type (train_type_id, station_id)
+VALUES ('f5eef54f-b85c-4109-b26b-18e15c8e075f', 'f41417ab-ef19-408f-ba69-5d3a1bc42e23');
+INSERT INTO station_train_type (train_type_id, station_id)
+VALUES ('8b4acd34-5738-49d4-918f-7d4955ded0bf', 'f41417ab-ef19-408f-ba69-5d3a1bc42e23');
+
+-- Stations
+INSERT INTO train (uuid, uri, title, description, keywords, metadata, status, last_contact_at, train_garage_id,
+ created_at, updated_at)
+VALUES ('e816b968-2663-4110-889d-7023b797c407', 'http://example.com/fds/train1',
+ 'SPARQL query to retrieve COVID 19 data',
+ 'SPARQL query to retrieve COVID 19 cases per region in the Netherlands', 'Health,COVID,SPARQL', '', 'SYNCED',
+ NOW(), 'a3bad37e-9333-4963-88ca-7c0b05a61ee8', NOW(), NOW());
+INSERT INTO train_train_type (train_type_id, train_id)
+VALUES ('f5eef54f-b85c-4109-b26b-18e15c8e075f', 'e816b968-2663-4110-889d-7023b797c407');
+
+INSERT INTO train (uuid, uri, title, description, keywords, metadata, status, last_contact_at, train_garage_id,
+ created_at, updated_at)
+VALUES ('5dd5c8d4-d084-4ade-8647-cdf0df0702c2', 'http://example.com/fds/train2', 'SQL Train for nice table',
+ 'SQL train creating very nice table for health data', 'Health,SQL,Table', '', 'SYNCED', NOW(),
+ 'a3bad37e-9333-4963-88ca-7c0b05a61ee8', NOW(), NOW());
+INSERT INTO train_train_type (train_type_id, train_id)
+VALUES ('32d36066-dbf6-4ed3-b4da-aad0edd1639f', '5dd5c8d4-d084-4ade-8647-cdf0df0702c2');
+
+INSERT INTO train (uuid, uri, title, description, keywords, metadata, status, last_contact_at, train_garage_id,
+ created_at, updated_at)
+VALUES ('a1eea8ce-e332-4e7b-b41d-880b53d709f4', 'http://example.com/fds/train3', 'SPARQL query summarizing COVID data',
+ 'SPARQL summary of COVID 19 cases for entire dataset', 'Health,COVID,SPARQL,Summary', '', 'SYNCED', NOW(),
+ 'a3bad37e-9333-4963-88ca-7c0b05a61ee8', NOW(), NOW());
+INSERT INTO train_train_type (train_type_id, train_id)
+VALUES ('f5eef54f-b85c-4109-b26b-18e15c8e075f', 'a1eea8ce-e332-4e7b-b41d-880b53d709f4');
diff --git a/src/main/resources/dev/db/migration/V0003.3__dev_data.sql b/src/main/resources/dev/db/migration/V0003.3__dev_data.sql
new file mode 100644
index 0000000..61a00e4
--- /dev/null
+++ b/src/main/resources/dev/db/migration/V0003.3__dev_data.sql
@@ -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.
+--
+
+INSERT INTO public.plan (uuid, display_name, note, train_id, created_at, updated_at)
+VALUES ('4556f410-d400-4bce-b8a3-baf255add4e0', 'SPARQL for COVID Portal',
+ 'This plan dispatches the COVID-SPARQL train to compatible stations...', 'a1eea8ce-e332-4e7b-b41d-880b53d709f4',
+ '2022-04-09 21:04:13.000000', '2022-04-09 21:04:13.000000');
+
+INSERT INTO public.plan_target (uuid, station_id, plan_id, created_at, updated_at)
+VALUES ('c3602f76-8018-494d-834f-ccfc5959ca7c', 'a4d6cf81-1e7a-4666-88e7-26fbdd992674',
+ '4556f410-d400-4bce-b8a3-baf255add4e0', '2022-04-09 21:04:50.000000', '2022-04-09 21:04:50.000000');
+INSERT INTO public.plan_target (uuid, station_id, plan_id, created_at, updated_at)
+VALUES ('edf65259-d79f-403c-83a9-452ea3905655', 'f41417ab-ef19-408f-ba69-5d3a1bc42e23',
+ '4556f410-d400-4bce-b8a3-baf255add4e0', '2022-04-09 21:05:21.000000', '2022-04-09 21:05:21.000000');
+
+INSERT INTO public.run (uuid, display_name, note, status, should_start_at, started_at, finished_at, plan_id, created_at,
+ updated_at)
+VALUES ('53135c4a-fa97-49e8-8c55-5e9a3f10d8c4', 'SPARQL for COVID Portal #1', 'Trying this for the first time...',
+ 'FINISHED', null, '2022-04-09 21:07:07.000000', '2022-04-09 21:09:16.000000',
+ '4556f410-d400-4bce-b8a3-baf255add4e0', '2022-04-09 21:07:26.000000', '2022-04-09 21:07:26.000000');
+
+INSERT INTO public.job (uuid, secret, remote_id, status, started_at, finished_at, plan_target_id, run_id, created_at,
+ updated_at)
+VALUES ('633879bd-df36-4c93-b455-6b9a56321771', 'verySecret', 'someRemoteId', 'FINISHED', '2022-04-09 21:10:43.000000',
+ '2022-04-09 22:03:47.000000', 'c3602f76-8018-494d-834f-ccfc5959ca7c', '53135c4a-fa97-49e8-8c55-5e9a3f10d8c4',
+ '2022-04-09 21:09:07.000000', '2022-04-09 21:09:07.000000');
+INSERT INTO public.job (uuid, secret, remote_id, status, started_at, finished_at, plan_target_id, run_id, created_at,
+ updated_at)
+VALUES ('0f8fa3ca-02b6-4cd3-b346-93b156166554', 'anotherSecret', 'anotherRemoteId', 'FINISHED',
+ '2022-04-09 21:15:40.000000', '2022-04-09 21:56:44.000000', 'edf65259-d79f-403c-83a9-452ea3905655',
+ '53135c4a-fa97-49e8-8c55-5e9a3f10d8c4', '2022-04-09 21:09:56.000000', '2022-04-09 21:09:56.000000');
+
+INSERT INTO public.job_event (uuid, type, result_status, occurred_at, message, payload, job_id, created_at, updated_at)
+VALUES ('0fc29c4a-f099-46a7-8d66-90cf76eef59b', 'INFO', '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, type, result_status, occurred_at, message, payload, job_id, created_at, updated_at)
+VALUES ('e035028e-57ab-4baa-adff-2aa5e1fb04c2', 'INFO', '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, type, result_status, occurred_at, message, payload, job_id, created_at, updated_at)
+VALUES ('4fe592ca-b36a-46e5-8f3b-c7b169eb0269', 'INFO', 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, type, result_status, occurred_at, message, payload, job_id, created_at, updated_at)
+VALUES ('0c6f3cca-f1a7-46db-874b-955cdd3abccc', 'INFO', 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, type, result_status, occurred_at, message, payload, job_id, created_at, updated_at)
+VALUES ('80b6bfbb-d128-4af2-ac90-849b3574735b', 'INFO', '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_event (uuid, type, result_status, occurred_at, message, payload, job_id, created_at, updated_at)
+VALUES ('7a35a0a8-ed96-48a8-84e0-9718778a8f21', 'ARTEFACT', null, '2022-04-09 21:22:50.000000',
+ 'Sending the result to handler', '{
+ "contentType": "text/plain",
+ "base64": false,
+ "content": "Hello, world!"
+ }', '633879bd-df36-4c93-b455-6b9a56321771', '2022-04-09 21:25:13.000000', '2022-04-09 21:25:13.000000');
diff --git a/src/main/resources/dev/db/migration/V0003.5__dev_data.sql b/src/main/resources/dev/db/migration/V0003.5__dev_data.sql
new file mode 100644
index 0000000..662afc6
--- /dev/null
+++ b/src/main/resources/dev/db/migration/V0003.5__dev_data.sql
@@ -0,0 +1,26 @@
+--
+-- 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.
+--
+
+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/dev/db/migration/V0003.7__dev_data.sql b/src/main/resources/dev/db/migration/V0003.7__dev_data.sql
new file mode 100644
index 0000000..8e43df7
--- /dev/null
+++ b/src/main/resources/dev/db/migration/V0003.7__dev_data.sql
@@ -0,0 +1,28 @@
+--
+-- 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.
+--
+
+UPDATE public.run SET version = 1649539513000 WHERE uuid = '53135c4a-fa97-49e8-8c55-5e9a3f10d8c4';
+
+UPDATE public.job SET version = 1649539513000 WHERE uuid = '633879bd-df36-4c93-b455-6b9a56321771';
+
+UPDATE public.job SET version = 1649538596000 WHERE uuid = '0f8fa3ca-02b6-4cd3-b346-93b156166554';
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/trainhandler/acceptance/WebIntegrationTest.java b/src/test/java/org/fairdatatrain/trainhandler/acceptance/WebIntegrationTest.java
new file mode 100644
index 0000000..f62f0af
--- /dev/null
+++ b/src/test/java/org/fairdatatrain/trainhandler/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.trainhandler.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/trainhandler/acceptance/actuator/ActuatorInfoDTO.java b/src/test/java/org/fairdatatrain/trainhandler/acceptance/actuator/ActuatorInfoDTO.java
new file mode 100644
index 0000000..edccb02
--- /dev/null
+++ b/src/test/java/org/fairdatatrain/trainhandler/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.trainhandler.acceptance.actuator;
+
+import com.fasterxml.jackson.annotation.JsonIgnoreProperties;
+import lombok.AllArgsConstructor;
+import lombok.Getter;
+import lombok.NoArgsConstructor;
+import lombok.Setter;
+
+import javax.validation.constraints.NotBlank;
+import javax.validation.constraints.NotNull;
+
+@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/trainhandler/acceptance/actuator/CorsTest.java b/src/test/java/org/fairdatatrain/trainhandler/acceptance/actuator/CorsTest.java
new file mode 100644
index 0000000..db43376
--- /dev/null
+++ b/src/test/java/org/fairdatatrain/trainhandler/acceptance/actuator/CorsTest.java
@@ -0,0 +1,77 @@
+/**
+ * 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.trainhandler.acceptance.actuator;
+
+import org.fairdatatrain.trainhandler.acceptance.WebIntegrationTest;
+import org.fairdatatrain.trainhandler.acceptance.actuator.ActuatorInfoDTO;
+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.HttpHeaders;
+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("OPTIONS /actuator/info")
+public class CorsTest extends WebIntegrationTest {
+
+
+ private URI url() {
+ return URI.create("/actuator/info");
+ }
+
+ @Test
+ @DisplayName("Correct CORS headers")
+ public void corsHeaders() {
+ // GIVEN:
+ RequestEntity request = RequestEntity
+ .options(url())
+ .build();
+ ParameterizedTypeReference responseType = new ParameterizedTypeReference<>() {
+ };
+
+ // WHEN:
+ ResponseEntity result = client.exchange(request, responseType);
+
+ // THEN:
+ assertThat(result.getHeaders().get(HttpHeaders.ACCESS_CONTROL_ALLOW_ORIGIN), is(notNullValue()));
+ assertThat(result.getHeaders().get(HttpHeaders.ACCESS_CONTROL_ALLOW_ORIGIN).size(), is(equalTo(1)));
+ assertThat(result.getHeaders().get(HttpHeaders.ACCESS_CONTROL_ALLOW_ORIGIN).get(0), is(equalTo("*")));
+ assertThat(result.getHeaders().get(HttpHeaders.ACCESS_CONTROL_ALLOW_METHODS), is(notNullValue()));
+ assertThat(result.getHeaders().get(HttpHeaders.ACCESS_CONTROL_ALLOW_METHODS).size(), is(equalTo(1)));
+ assertThat(result.getHeaders().get(HttpHeaders.ACCESS_CONTROL_ALLOW_METHODS).get(0), is(equalTo("GET,DELETE,PATCH,POST,PUT")));
+ assertThat(result.getHeaders().get(HttpHeaders.ACCESS_CONTROL_ALLOW_HEADERS), is(notNullValue()));
+ assertThat(result.getHeaders().get(HttpHeaders.ACCESS_CONTROL_ALLOW_HEADERS).size(), is(equalTo(1)));
+ assertThat(result.getHeaders().get(HttpHeaders.ACCESS_CONTROL_ALLOW_HEADERS).get(0), is(equalTo("Accept,Authorization,Content-Type,Origin")));
+ assertThat(result.getHeaders().get(HttpHeaders.ACCESS_CONTROL_EXPOSE_HEADERS), is(notNullValue()));
+ assertThat(result.getHeaders().get(HttpHeaders.ACCESS_CONTROL_EXPOSE_HEADERS).size(), is(equalTo(1)));
+ assertThat(result.getHeaders().get(HttpHeaders.ACCESS_CONTROL_EXPOSE_HEADERS).get(0), is(equalTo("Link,Location")));
+ }
+}
diff --git a/src/test/java/org/fairdatatrain/trainhandler/acceptance/actuator/List_Info_GET.java b/src/test/java/org/fairdatatrain/trainhandler/acceptance/actuator/List_Info_GET.java
new file mode 100644
index 0000000..6f1123d
--- /dev/null
+++ b/src/test/java/org/fairdatatrain/trainhandler/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.trainhandler.acceptance.actuator;
+
+import org.fairdatatrain.trainhandler.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)));
+ }
+
+}
diff --git a/src/test/java/org/fairdatatrain/trainhandler/acceptance/helper/HelperPage.java b/src/test/java/org/fairdatatrain/trainhandler/acceptance/helper/HelperPage.java
new file mode 100644
index 0000000..7366280
--- /dev/null
+++ b/src/test/java/org/fairdatatrain/trainhandler/acceptance/helper/HelperPage.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.trainhandler.acceptance.helper;
+
+import lombok.AllArgsConstructor;
+import lombok.Getter;
+import lombok.NoArgsConstructor;
+import lombok.Setter;
+
+import java.util.List;
+
+@AllArgsConstructor
+@NoArgsConstructor
+@Getter
+@Setter
+public class HelperPage {
+
+ private List content;
+
+ private int number;
+
+ private int size;
+
+ private int totalPages;
+
+ private int totalElements;
+
+ private int numberOfElements;
+
+ private boolean first;
+
+ private boolean last;
+
+ private boolean empty;
+
+}
diff --git a/src/test/java/org/fairdatatrain/trainhandler/acceptance/openapi/OpenApiDocs_GET.java b/src/test/java/org/fairdatatrain/trainhandler/acceptance/openapi/OpenApiDocs_GET.java
new file mode 100644
index 0000000..0e268d5
--- /dev/null
+++ b/src/test/java/org/fairdatatrain/trainhandler/acceptance/openapi/OpenApiDocs_GET.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.trainhandler.acceptance.openapi;
+import org.fairdatatrain.trainhandler.acceptance.WebIntegrationTest;
+import org.junit.jupiter.api.DisplayName;
+import org.junit.jupiter.api.Test;
+import org.springframework.core.ParameterizedTypeReference;
+import org.springframework.http.HttpStatus;
+import org.springframework.http.MediaType;
+import org.springframework.http.RequestEntity;
+import org.springframework.http.ResponseEntity;
+
+import java.net.URI;
+
+import static org.hamcrest.MatcherAssert.assertThat;
+import static org.hamcrest.core.Is.is;
+import static org.hamcrest.core.IsEqual.equalTo;
+
+@DisplayName("GET /v3/api-docs")
+public class OpenApiDocs_GET extends WebIntegrationTest {
+
+ private URI url() {
+ return URI.create("/v3/api-docs");
+ }
+
+ @Test
+ @DisplayName("HTTP 200: API Docs")
+ public void res200_apiDocs() {
+ // GIVEN
+ RequestEntity> request = RequestEntity
+ .get(url())
+ .accept(MediaType.APPLICATION_JSON)
+ .build();
+
+ // WHEN
+ ResponseEntity result = client.exchange(request, new ParameterizedTypeReference<>() {});
+
+ // THEN
+ assertThat("Response code is OK", result.getStatusCode(), is(equalTo(HttpStatus.OK)));
+ assertThat("Response is JSON", result.getHeaders().getContentType(), is(equalTo(MediaType.APPLICATION_JSON)));
+ }
+}
diff --git a/src/test/java/org/fairdatatrain/trainhandler/acceptance/openapi/SwaggerUI_GET.java b/src/test/java/org/fairdatatrain/trainhandler/acceptance/openapi/SwaggerUI_GET.java
new file mode 100644
index 0000000..61124f1
--- /dev/null
+++ b/src/test/java/org/fairdatatrain/trainhandler/acceptance/openapi/SwaggerUI_GET.java
@@ -0,0 +1,87 @@
+/**
+ * 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.trainhandler.acceptance.openapi;
+
+import org.fairdatatrain.trainhandler.acceptance.WebIntegrationTest;
+import org.junit.jupiter.api.DisplayName;
+import org.junit.jupiter.api.Test;
+import org.springframework.boot.test.web.client.TestRestTemplate;
+import org.springframework.core.ParameterizedTypeReference;
+import org.springframework.http.HttpStatus;
+import org.springframework.http.MediaType;
+import org.springframework.http.RequestEntity;
+import org.springframework.http.ResponseEntity;
+
+import java.net.URI;
+
+import static org.hamcrest.MatcherAssert.assertThat;
+import static org.hamcrest.core.Is.is;
+import static org.hamcrest.core.IsEqual.equalTo;
+import static org.hamcrest.core.IsNull.notNullValue;
+
+@DisplayName("GET /swagger-ui.html")
+public class SwaggerUI_GET extends WebIntegrationTest {
+
+ private URI baseUrl() {
+ return URI.create("/swagger-ui.html");
+ }
+
+ private URI redirectedUrl() {
+ return URI.create("/swagger-ui/index.html?configUrl=/v3/api-docs/swagger-config");
+ }
+/*
+ @Test
+ @DisplayName("HTTP 302: Redirects to Swagger UI")
+ public void res302_redirectsToSwaggerUI() {
+ // GIVEN
+ RequestEntity> request = RequestEntity
+ .get(baseUrl())
+ .accept(MediaType.TEXT_HTML)
+ .build();
+
+ // WHEN
+ ResponseEntity result = client.exchange(request, new ParameterizedTypeReference<>() {});
+
+ // THEN
+ assertThat("Response code is FOUND", result.getStatusCode(), is(equalTo(HttpStatus.FOUND)));
+ assertThat("Contains Location header", result.getHeaders().getLocation(), is(notNullValue()));
+ assertThat("Contains correct Location header", result.getHeaders().getLocation().toString().endsWith(redirectedUrl().toString()), is(Boolean.TRUE));
+ }
+*/
+ @Test
+ @DisplayName("HTTP 200: Swagger UI")
+ public void res200_swaggerUI() {
+ // GIVEN
+ RequestEntity> request = RequestEntity
+ .get(redirectedUrl())
+ .accept(MediaType.TEXT_HTML)
+ .build();
+
+ // WHEN
+ ResponseEntity result = client.exchange(request, new ParameterizedTypeReference<>() {});
+
+ // THEN
+ assertThat("Response code is OK", result.getStatusCode(), is(equalTo(HttpStatus.OK)));
+ assertThat("Response is Swagger UI in HTML", result.getHeaders().getContentType(), is(equalTo(MediaType.TEXT_HTML)));
+ }
+}
diff --git a/src/test/java/org/fairdatatrain/trainhandler/acceptance/stationdirectory/Common.java b/src/test/java/org/fairdatatrain/trainhandler/acceptance/stationdirectory/Common.java
new file mode 100644
index 0000000..25d0df0
--- /dev/null
+++ b/src/test/java/org/fairdatatrain/trainhandler/acceptance/stationdirectory/Common.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.trainhandler.acceptance.stationdirectory;
+
+import org.fairdatatrain.trainhandler.api.dto.stationdirectory.StationDirectoryChangeDTO;
+import org.fairdatatrain.trainhandler.api.dto.stationdirectory.StationDirectoryDTO;
+import org.fairdatatrain.trainhandler.data.model.StationDirectory;
+
+import static org.hamcrest.MatcherAssert.assertThat;
+import static org.hamcrest.Matchers.equalTo;
+import static org.hamcrest.Matchers.is;
+
+public class Common {
+
+ public static void assertEquals(StationDirectoryDTO dto, StationDirectory entity) {
+ assertThat(dto.getUuid(), is(equalTo(entity.getUuid())));
+ assertThat(dto.getDisplayName(), is(equalTo(entity.getDisplayName())));
+ assertThat(dto.getMetadata(), is(equalTo(entity.getMetadata())));
+ assertThat(dto.getUri(), is(equalTo(entity.getUri())));
+ assertThat(dto.getNote(), is(equalTo(entity.getNote())));
+ assertThat(dto.getStatus(), is(equalTo(entity.getStatus())));
+ }
+
+ public static void assertEquals(StationDirectoryDTO dto, StationDirectoryChangeDTO changeDto) {
+ assertThat(dto.getDisplayName(), is(equalTo(changeDto.getDisplayName())));
+ assertThat(dto.getUri(), is(equalTo(changeDto.getUri())));
+ assertThat(dto.getNote(), is(equalTo(changeDto.getNote())));
+ }
+}
diff --git a/src/test/java/org/fairdatatrain/trainhandler/acceptance/stationdirectory/Detail_DELETE.java b/src/test/java/org/fairdatatrain/trainhandler/acceptance/stationdirectory/Detail_DELETE.java
new file mode 100644
index 0000000..f354854
--- /dev/null
+++ b/src/test/java/org/fairdatatrain/trainhandler/acceptance/stationdirectory/Detail_DELETE.java
@@ -0,0 +1,98 @@
+/**
+ * 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.trainhandler.acceptance.stationdirectory;
+
+import org.fairdatatrain.trainhandler.acceptance.WebIntegrationTest;
+import org.fairdatatrain.trainhandler.data.model.StationDirectory;
+import org.fairdatatrain.trainhandler.data.repository.StationDirectoryRepository;
+import org.junit.jupiter.api.DisplayName;
+import org.junit.jupiter.api.Test;
+import org.springframework.beans.factory.annotation.Autowired;
+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 java.util.Optional;
+import java.util.UUID;
+
+import static java.lang.String.format;
+import static org.hamcrest.MatcherAssert.assertThat;
+import static org.hamcrest.Matchers.equalTo;
+import static org.hamcrest.Matchers.is;
+
+@DisplayName("DELETE /station-directories/:uuid")
+public class Detail_DELETE extends WebIntegrationTest {
+
+ private URI url(UUID uuid) {
+ return URI.create(format("/station-directories/%s", uuid));
+ }
+
+ @Autowired
+ private StationDirectoryRepository stationDirectoryRepository;
+
+ @Test
+ @DisplayName("HTTP 204")
+ public void res204() {
+ // GIVEN: prepare data
+ stationDirectoryRepository.deleteAll();
+ StationDirectory directory = stationDirectoryRepository.save(StationDirectoryTestFixtures.STATION_DIRECTORY_A);
+
+ // AND: prepare request
+ RequestEntity request = RequestEntity
+ .delete(url(directory.getUuid()))
+ .build();
+ ParameterizedTypeReference responseType = new ParameterizedTypeReference<>() {
+ };
+
+ // WHEN:
+ ResponseEntity result = client.exchange(request, responseType);
+
+ // THEN:
+ assertThat("Correct response code is received", result.getStatusCode(), is(equalTo(HttpStatus.NO_CONTENT)));
+ Optional directoryAfter = stationDirectoryRepository.findById(directory.getUuid());
+ assertThat("Station directory is deleted", directoryAfter.isEmpty(), is(equalTo(true)));
+ }
+
+ @Test
+ @DisplayName("HTTP 404")
+ public void res404() {
+ // GIVEN: prepare data
+ stationDirectoryRepository.deleteAll();
+
+ // AND: prepare request
+ RequestEntity request = RequestEntity
+ .delete(url(UUID.randomUUID()))
+ .build();
+ ParameterizedTypeReference> responseType = new ParameterizedTypeReference<>() {
+ };
+
+ // WHEN:
+ ResponseEntity> result = client.exchange(request, responseType);
+
+ // THEN:
+ assertThat("Correct response code is received", result.getStatusCode(), is(equalTo(HttpStatus.NOT_FOUND)));
+ }
+
+}
diff --git a/src/test/java/org/fairdatatrain/trainhandler/acceptance/stationdirectory/Detail_GET.java b/src/test/java/org/fairdatatrain/trainhandler/acceptance/stationdirectory/Detail_GET.java
new file mode 100644
index 0000000..fff0777
--- /dev/null
+++ b/src/test/java/org/fairdatatrain/trainhandler/acceptance/stationdirectory/Detail_GET.java
@@ -0,0 +1,97 @@
+/**
+ * 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.trainhandler.acceptance.stationdirectory;
+
+import org.fairdatatrain.trainhandler.acceptance.WebIntegrationTest;
+import org.fairdatatrain.trainhandler.api.dto.stationdirectory.StationDirectoryDTO;
+import org.fairdatatrain.trainhandler.data.model.StationDirectory;
+import org.fairdatatrain.trainhandler.data.repository.StationDirectoryRepository;
+import org.junit.jupiter.api.DisplayName;
+import org.junit.jupiter.api.Test;
+import org.springframework.beans.factory.annotation.Autowired;
+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 java.util.UUID;
+
+import static java.lang.String.format;
+import static org.hamcrest.MatcherAssert.assertThat;
+import static org.hamcrest.Matchers.*;
+
+@DisplayName("GET /station-directories/:uuid")
+public class Detail_GET extends WebIntegrationTest {
+
+ private URI url(UUID uuid) {
+ return URI.create(format("/station-directories/%s", uuid));
+ }
+
+ @Autowired
+ private StationDirectoryRepository stationDirectoryRepository;
+
+ @Test
+ @DisplayName("HTTP 200")
+ public void res200() {
+ // GIVEN: prepare data
+ stationDirectoryRepository.deleteAll();
+ StationDirectory directory = stationDirectoryRepository.save(StationDirectoryTestFixtures.STATION_DIRECTORY_A);
+
+ // AND: prepare request
+ RequestEntity request = RequestEntity
+ .get(url(directory.getUuid()))
+ .build();
+ ParameterizedTypeReference responseType = new ParameterizedTypeReference<>() {
+ };
+
+ // WHEN:
+ ResponseEntity result = client.exchange(request, responseType);
+
+ // THEN:
+ assertThat("Correct response code is received", result.getStatusCode(), is(equalTo(HttpStatus.OK)));
+ assertThat("Body is not null", result.getBody(), is(notNullValue()));
+ Common.assertEquals(result.getBody(), directory);
+ }
+
+ @Test
+ @DisplayName("HTTP 404")
+ public void res404() {
+ // GIVEN: prepare data
+ stationDirectoryRepository.deleteAll();
+
+ // AND: prepare request
+ RequestEntity request = RequestEntity
+ .get(url(UUID.randomUUID()))
+ .build();
+ ParameterizedTypeReference> responseType = new ParameterizedTypeReference<>() {
+ };
+
+ // WHEN:
+ ResponseEntity> result = client.exchange(request, responseType);
+
+ // THEN:
+ assertThat("Correct response code is received", result.getStatusCode(), is(equalTo(HttpStatus.NOT_FOUND)));
+ }
+
+}
diff --git a/src/test/java/org/fairdatatrain/trainhandler/acceptance/stationdirectory/Detail_PUT.java b/src/test/java/org/fairdatatrain/trainhandler/acceptance/stationdirectory/Detail_PUT.java
new file mode 100644
index 0000000..867220b
--- /dev/null
+++ b/src/test/java/org/fairdatatrain/trainhandler/acceptance/stationdirectory/Detail_PUT.java
@@ -0,0 +1,134 @@
+/**
+ * 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.trainhandler.acceptance.stationdirectory;
+
+import org.fairdatatrain.trainhandler.acceptance.WebIntegrationTest;
+import org.fairdatatrain.trainhandler.api.dto.stationdirectory.StationDirectoryChangeDTO;
+import org.fairdatatrain.trainhandler.api.dto.stationdirectory.StationDirectoryDTO;
+import org.fairdatatrain.trainhandler.data.model.StationDirectory;
+import org.fairdatatrain.trainhandler.data.repository.StationDirectoryRepository;
+import org.junit.jupiter.api.DisplayName;
+import org.junit.jupiter.api.Test;
+import org.springframework.beans.factory.annotation.Autowired;
+import org.springframework.core.ParameterizedTypeReference;
+import org.springframework.http.HttpStatus;
+import org.springframework.http.MediaType;
+import org.springframework.http.RequestEntity;
+import org.springframework.http.ResponseEntity;
+
+import java.net.URI;
+import java.util.Optional;
+import java.util.UUID;
+
+import static java.lang.String.format;
+import static org.hamcrest.MatcherAssert.assertThat;
+import static org.hamcrest.Matchers.*;
+
+@DisplayName("PUT /station-directories/:uuid")
+public class Detail_PUT extends WebIntegrationTest {
+
+ private URI url(UUID uuid) {
+ return URI.create(format("/station-directories/%s", uuid));
+ }
+
+ @Autowired
+ private StationDirectoryRepository stationDirectoryRepository;
+
+ @Test
+ @DisplayName("HTTP 200")
+ public void res200() {
+ // GIVEN: prepare data
+ stationDirectoryRepository.deleteAll();
+ StationDirectory directory = stationDirectoryRepository.save(StationDirectoryTestFixtures.STATION_DIRECTORY_A);
+ StationDirectoryChangeDTO reqDto = StationDirectoryTestFixtures.STATION_DIRECTORY_A_UPDATE;
+
+ // AND: prepare request
+ RequestEntity request = RequestEntity
+ .put(url(directory.getUuid()))
+ .accept(MediaType.APPLICATION_JSON)
+ .body(reqDto);
+ ParameterizedTypeReference responseType = new ParameterizedTypeReference<>() {
+ };
+
+ // WHEN:
+ ResponseEntity result = client.exchange(request, responseType);
+
+ // THEN:
+ assertThat("Correct response code is received", result.getStatusCode(), is(equalTo(HttpStatus.OK)));
+ assertThat("Body is not null", result.getBody(), is(notNullValue()));
+ Common.assertEquals(result.getBody(), reqDto);
+ Optional directoryAfter = stationDirectoryRepository.findById(directory.getUuid());
+ assertThat("Station directory is saved", directoryAfter.isPresent(), is(equalTo(true)));
+ Common.assertEquals(result.getBody(), directoryAfter.get());
+ }
+
+ @Test
+ @DisplayName("HTTP 400")
+ public void res400() {
+ // GIVEN: prepare data
+ stationDirectoryRepository.deleteAll();
+ StationDirectory directory = stationDirectoryRepository.save(StationDirectoryTestFixtures.STATION_DIRECTORY_A);
+ StationDirectoryChangeDTO reqDto =
+ StationDirectoryTestFixtures.STATION_DIRECTORY_A_UPDATE
+ .toBuilder()
+ .displayName("")
+ .build();
+
+ // AND: prepare request
+ RequestEntity request = RequestEntity
+ .put(url(directory.getUuid()))
+ .accept(MediaType.APPLICATION_JSON)
+ .body(reqDto);
+ ParameterizedTypeReference> responseType = new ParameterizedTypeReference<>() {
+ };
+
+ // WHEN:
+ ResponseEntity> result = client.exchange(request, responseType);
+
+ // THEN:
+ assertThat("Correct response code is received", result.getStatusCode(), is(equalTo(HttpStatus.BAD_REQUEST)));
+ }
+
+ @Test
+ @DisplayName("HTTP 404")
+ public void res404() {
+ // GIVEN: prepare data
+ stationDirectoryRepository.deleteAll();
+ StationDirectoryChangeDTO reqDto = StationDirectoryTestFixtures.STATION_DIRECTORY_A_UPDATE;
+
+ // AND: prepare request
+ RequestEntity request = RequestEntity
+ .put(url(UUID.randomUUID()))
+ .accept(MediaType.APPLICATION_JSON)
+ .body(reqDto);
+ ParameterizedTypeReference> responseType = new ParameterizedTypeReference<>() {
+ };
+
+ // WHEN:
+ ResponseEntity> result = client.exchange(request, responseType);
+
+ // THEN:
+ assertThat("Correct response code is received", result.getStatusCode(), is(equalTo(HttpStatus.NOT_FOUND)));
+ }
+
+}
diff --git a/src/test/java/org/fairdatatrain/trainhandler/acceptance/stationdirectory/List_GET.java b/src/test/java/org/fairdatatrain/trainhandler/acceptance/stationdirectory/List_GET.java
new file mode 100644
index 0000000..601a180
--- /dev/null
+++ b/src/test/java/org/fairdatatrain/trainhandler/acceptance/stationdirectory/List_GET.java
@@ -0,0 +1,142 @@
+/**
+ * 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.trainhandler.acceptance.stationdirectory;
+
+import org.fairdatatrain.trainhandler.acceptance.WebIntegrationTest;
+import org.fairdatatrain.trainhandler.acceptance.helper.HelperPage;
+import org.fairdatatrain.trainhandler.api.dto.stationdirectory.StationDirectoryDTO;
+import org.fairdatatrain.trainhandler.data.model.StationDirectory;
+import org.fairdatatrain.trainhandler.data.repository.StationDirectoryRepository;
+import org.junit.jupiter.api.DisplayName;
+import org.junit.jupiter.api.Test;
+import org.springframework.beans.factory.annotation.Autowired;
+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 java.util.List;
+import java.util.stream.StreamSupport;
+
+import static java.lang.String.format;
+import static org.hamcrest.MatcherAssert.assertThat;
+import static org.hamcrest.Matchers.*;
+
+@DisplayName("GET /station-directories")
+public class List_GET extends WebIntegrationTest {
+
+ private URI url() {
+ return URI.create("/station-directories");
+ }
+
+ private URI urlPaged(int page, int size) {
+ return URI.create(format("/station-directories?page=%d&size=%d", page, size));
+ }
+
+ @Autowired
+ private StationDirectoryRepository stationDirectoryRepository;
+
+ @Test
+ @DisplayName("HTTP 200: empty")
+ public void res200_empty() {
+ // GIVEN: prepare data
+ stationDirectoryRepository.deleteAll();
+
+ // AND: prepare request
+ RequestEntity request = RequestEntity
+ .get(url())
+ .build();
+ ParameterizedTypeReference> responseType = new ParameterizedTypeReference<>() {
+ };
+
+ // WHEN:
+ ResponseEntity> result = client.exchange(request, responseType);
+
+ // THEN:
+ assertThat("Correct response code is received", result.getStatusCode(), is(equalTo(HttpStatus.OK)));
+ assertThat("Body is not null", result.getBody(), is(notNullValue()));
+ assertThat("Results page is declared as empty", result.getBody().isEmpty(), is(equalTo(true)));
+ assertThat("Results page is empty", result.getBody().getContent().isEmpty(), is(equalTo(true)));
+ }
+
+ @Test
+ @DisplayName("HTTP 200: non-empty")
+ public void res200_nonEmpty() {
+ // GIVEN: prepare data
+ stationDirectoryRepository.deleteAll();
+ List stationDirectories = StreamSupport.stream(
+ stationDirectoryRepository.saveAll(StationDirectoryTestFixtures.STATION_DIRECTORIES).spliterator(),
+ false
+ ).toList();
+
+ // AND: prepare request
+ RequestEntity request = RequestEntity
+ .get(url())
+ .build();
+ ParameterizedTypeReference> responseType = new ParameterizedTypeReference<>() {
+ };
+
+ // WHEN:
+ ResponseEntity> result = client.exchange(request, responseType);
+
+ // THEN:
+ assertThat("Correct response code is received", result.getStatusCode(), is(equalTo(HttpStatus.OK)));
+ assertThat("Body is not null", result.getBody(), is(notNullValue()));
+ assertThat("Results page is declared as not-empty", result.getBody().isEmpty(), is(equalTo(false)));
+ assertThat("Results page has declared correct number of elements", result.getBody().getNumberOfElements(), comparesEqualTo(stationDirectories.size()));
+ assertThat("Results page has declared correct total elements", result.getBody().getTotalElements(), comparesEqualTo(stationDirectories.size()));
+ assertThat("Results page has correct size", result.getBody().getContent().size(), comparesEqualTo(stationDirectories.size()));
+ }
+
+ @Test
+ @DisplayName("HTTP 200: multi-page")
+ public void res200_multiPage() {
+ // GIVEN: prepare data
+ stationDirectoryRepository.deleteAll();
+ List stationDirectories = StreamSupport.stream(
+ stationDirectoryRepository.saveAll(StationDirectoryTestFixtures.STATION_DIRECTORIES).spliterator(),
+ false
+ ).toList();
+
+ // AND: prepare request
+ RequestEntity request = RequestEntity
+ .get(urlPaged(2,1))
+ .build();
+ ParameterizedTypeReference> responseType = new ParameterizedTypeReference<>() {
+ };
+
+ // WHEN:
+ ResponseEntity> result = client.exchange(request, responseType);
+
+ // THEN:
+ assertThat("Correct response code is received", result.getStatusCode(), is(equalTo(HttpStatus.OK)));
+ assertThat("Body is not null", result.getBody(), is(notNullValue()));
+ assertThat("Results page is declared as not-empty", result.getBody().isEmpty(), is(equalTo(false)));
+ assertThat("Results page has declared current page number", result.getBody().getNumber(), comparesEqualTo(2));
+ assertThat("Results page has declared correct number of elements", result.getBody().getNumberOfElements(), comparesEqualTo(1));
+ assertThat("Results page has declared correct total elements", result.getBody().getTotalElements(), comparesEqualTo(stationDirectories.size()));
+ assertThat("Results page has correct size", result.getBody().getContent().size(), comparesEqualTo(1));
+ }
+
+}
diff --git a/src/test/java/org/fairdatatrain/trainhandler/acceptance/stationdirectory/List_POST.java b/src/test/java/org/fairdatatrain/trainhandler/acceptance/stationdirectory/List_POST.java
new file mode 100644
index 0000000..eb9820e
--- /dev/null
+++ b/src/test/java/org/fairdatatrain/trainhandler/acceptance/stationdirectory/List_POST.java
@@ -0,0 +1,106 @@
+/**
+ * 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.trainhandler.acceptance.stationdirectory;
+
+import org.fairdatatrain.trainhandler.acceptance.WebIntegrationTest;
+import org.fairdatatrain.trainhandler.api.dto.stationdirectory.StationDirectoryChangeDTO;
+import org.fairdatatrain.trainhandler.api.dto.stationdirectory.StationDirectoryDTO;
+import org.fairdatatrain.trainhandler.data.model.StationDirectory;
+import org.fairdatatrain.trainhandler.data.repository.StationDirectoryRepository;
+import org.junit.jupiter.api.DisplayName;
+import org.junit.jupiter.api.Test;
+import org.springframework.beans.factory.annotation.Autowired;
+import org.springframework.core.ParameterizedTypeReference;
+import org.springframework.http.HttpStatus;
+import org.springframework.http.MediaType;
+import org.springframework.http.RequestEntity;
+import org.springframework.http.ResponseEntity;
+
+import java.net.URI;
+import java.util.Optional;
+
+import static org.hamcrest.MatcherAssert.assertThat;
+import static org.hamcrest.Matchers.*;
+
+@DisplayName("POST /station-directories")
+public class List_POST extends WebIntegrationTest {
+
+ private URI url() {
+ return URI.create("/station-directories");
+ }
+
+ @Autowired
+ private StationDirectoryRepository stationDirectoryRepository;
+
+ @Test
+ @DisplayName("HTTP 200")
+ public void res200() {
+ // GIVEN: Prepare data
+ stationDirectoryRepository.deleteAll();
+ StationDirectoryChangeDTO reqDto = StationDirectoryTestFixtures.STATION_DIRECTORY_X_CREATE;
+
+ // AND: Prepare request
+ RequestEntity request = RequestEntity
+ .post(url())
+ .accept(MediaType.APPLICATION_JSON)
+ .body(reqDto);
+ ParameterizedTypeReference responseType = new ParameterizedTypeReference<>() {
+ };
+
+ // WHEN:
+ ResponseEntity result = client.exchange(request, responseType);
+
+ // THEN:
+ assertThat("Correct response code is received", result.getStatusCode(), is(equalTo(HttpStatus.CREATED)));
+ assertThat("Body is not null", result.getBody(), is(notNullValue()));
+ Optional oStationDirectory = stationDirectoryRepository.findById(result.getBody().getUuid());
+ assertThat("Entity is saved with UUID", oStationDirectory.isPresent(), is(equalTo(true)));
+ Common.assertEquals(result.getBody(), oStationDirectory.get());
+ }
+
+ @Test
+ @DisplayName("HTTP 400: invalid")
+ public void res400_invalid() {
+ // GIVEN: Prepare data
+ stationDirectoryRepository.deleteAll();
+ StationDirectoryChangeDTO reqDto =
+ StationDirectoryTestFixtures.STATION_DIRECTORY_X_CREATE
+ .toBuilder()
+ .displayName("")
+ .build();
+
+ // AND: Prepare request
+ RequestEntity request = RequestEntity
+ .post(url())
+ .accept(MediaType.APPLICATION_JSON)
+ .body(reqDto);
+ ParameterizedTypeReference> responseType = new ParameterizedTypeReference<>() {
+ };
+
+ // WHEN:
+ ResponseEntity> result = client.exchange(request, responseType);
+
+ // THEN:
+ assertThat("Correct response code is received", result.getStatusCode(), is(equalTo(HttpStatus.BAD_REQUEST)));
+ }
+}
diff --git a/src/test/java/org/fairdatatrain/trainhandler/acceptance/stationdirectory/StationDirectoryTestFixtures.java b/src/test/java/org/fairdatatrain/trainhandler/acceptance/stationdirectory/StationDirectoryTestFixtures.java
new file mode 100644
index 0000000..abe0b8c
--- /dev/null
+++ b/src/test/java/org/fairdatatrain/trainhandler/acceptance/stationdirectory/StationDirectoryTestFixtures.java
@@ -0,0 +1,91 @@
+/**
+ * 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.trainhandler.acceptance.stationdirectory;
+
+import org.fairdatatrain.trainhandler.api.dto.stationdirectory.StationDirectoryChangeDTO;
+import org.fairdatatrain.trainhandler.data.model.StationDirectory;
+import org.fairdatatrain.trainhandler.data.model.enums.SyncServiceStatus;
+
+import java.sql.Timestamp;
+import java.time.Instant;
+import java.util.Arrays;
+import java.util.List;
+import java.util.UUID;
+
+public class StationDirectoryTestFixtures {
+
+ public static StationDirectory STATION_DIRECTORY_A =
+ StationDirectory.builder()
+ .uuid(UUID.randomUUID())
+ .createdAt(Timestamp.from(Instant.now()))
+ .updatedAt(Timestamp.from(Instant.now()))
+ .uri("https://example.com/fdt/station-directory/A")
+ .displayName("Station Directory A")
+ .note("")
+ .metadata("")
+ .status(SyncServiceStatus.SYNCED)
+ .lastContactAt(Timestamp.from(Instant.now()))
+ .build();
+
+ public static StationDirectory STATION_DIRECTORY_B =
+ StationDirectory.builder()
+ .uuid(UUID.randomUUID())
+ .createdAt(Timestamp.from(Instant.now()))
+ .updatedAt(Timestamp.from(Instant.now()))
+ .uri("https://example.com/fdt/station-directory/B")
+ .displayName("Station Directory B")
+ .note("")
+ .metadata("")
+ .status(SyncServiceStatus.SYNCED)
+ .lastContactAt(Timestamp.from(Instant.now()))
+ .build();
+
+ public static StationDirectory STATION_DIRECTORY_C =
+ StationDirectory.builder()
+ .uuid(UUID.randomUUID())
+ .createdAt(Timestamp.from(Instant.now()))
+ .updatedAt(Timestamp.from(Instant.now()))
+ .uri("https://example.com/fdt/station-directory/C")
+ .displayName("Station Directory C")
+ .note("")
+ .metadata("")
+ .status(SyncServiceStatus.SYNCED)
+ .lastContactAt(Timestamp.from(Instant.now()))
+ .build();
+
+ public static StationDirectoryChangeDTO STATION_DIRECTORY_X_CREATE = StationDirectoryChangeDTO
+ .builder()
+ .uri("https://example.com/fdt/station-directory/X")
+ .displayName("Station Directory X")
+ .note("My custom station directory")
+ .build();
+
+ public static StationDirectoryChangeDTO STATION_DIRECTORY_A_UPDATE = StationDirectoryChangeDTO
+ .builder()
+ .uri("https://example.com/fdt/station-directory/A/edit")
+ .displayName("Station Directory A: EDIT")
+ .note("My custom station directory edited")
+ .build();
+
+ public static List STATION_DIRECTORIES = Arrays.asList(STATION_DIRECTORY_A, STATION_DIRECTORY_B, STATION_DIRECTORY_C);
+}
diff --git a/src/test/java/org/fairdatatrain/trainhandler/acceptance/traingarage/Common.java b/src/test/java/org/fairdatatrain/trainhandler/acceptance/traingarage/Common.java
new file mode 100644
index 0000000..bf3c94f
--- /dev/null
+++ b/src/test/java/org/fairdatatrain/trainhandler/acceptance/traingarage/Common.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.trainhandler.acceptance.traingarage;
+
+import org.fairdatatrain.trainhandler.api.dto.traingarage.TrainGarageChangeDTO;
+import org.fairdatatrain.trainhandler.api.dto.traingarage.TrainGarageDTO;
+import org.fairdatatrain.trainhandler.data.model.TrainGarage;
+
+import static org.hamcrest.MatcherAssert.assertThat;
+import static org.hamcrest.Matchers.equalTo;
+import static org.hamcrest.Matchers.is;
+
+public class Common {
+
+ public static void assertEquals(TrainGarageDTO dto, TrainGarage entity) {
+ assertThat(dto.getUuid(), is(equalTo(entity.getUuid())));
+ assertThat(dto.getDisplayName(), is(equalTo(entity.getDisplayName())));
+ assertThat(dto.getMetadata(), is(equalTo(entity.getMetadata())));
+ assertThat(dto.getUri(), is(equalTo(entity.getUri())));
+ assertThat(dto.getNote(), is(equalTo(entity.getNote())));
+ assertThat(dto.getStatus(), is(equalTo(entity.getStatus())));
+ }
+
+ public static void assertEquals(TrainGarageDTO dto, TrainGarageChangeDTO changeDto) {
+ assertThat(dto.getDisplayName(), is(equalTo(changeDto.getDisplayName())));
+ assertThat(dto.getUri(), is(equalTo(changeDto.getUri())));
+ assertThat(dto.getNote(), is(equalTo(changeDto.getNote())));
+ }
+}
diff --git a/src/test/java/org/fairdatatrain/trainhandler/acceptance/traingarage/Detail_DELETE.java b/src/test/java/org/fairdatatrain/trainhandler/acceptance/traingarage/Detail_DELETE.java
new file mode 100644
index 0000000..2ce2f37
--- /dev/null
+++ b/src/test/java/org/fairdatatrain/trainhandler/acceptance/traingarage/Detail_DELETE.java
@@ -0,0 +1,98 @@
+/**
+ * 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.trainhandler.acceptance.traingarage;
+
+import org.fairdatatrain.trainhandler.acceptance.WebIntegrationTest;
+import org.fairdatatrain.trainhandler.data.model.TrainGarage;
+import org.fairdatatrain.trainhandler.data.repository.TrainGarageRepository;
+import org.junit.jupiter.api.DisplayName;
+import org.junit.jupiter.api.Test;
+import org.springframework.beans.factory.annotation.Autowired;
+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 java.util.Optional;
+import java.util.UUID;
+
+import static java.lang.String.format;
+import static org.hamcrest.MatcherAssert.assertThat;
+import static org.hamcrest.Matchers.equalTo;
+import static org.hamcrest.Matchers.is;
+
+@DisplayName("DELETE /train-garages/:uuid")
+public class Detail_DELETE extends WebIntegrationTest {
+
+ private URI url(UUID uuid) {
+ return URI.create(format("/train-garages/%s", uuid));
+ }
+
+ @Autowired
+ private TrainGarageRepository trainGarageRepository;
+
+ @Test
+ @DisplayName("HTTP 204")
+ public void res204() {
+ // GIVEN: prepare data
+ trainGarageRepository.deleteAll();
+ TrainGarage garage = trainGarageRepository.save(TrainGarageTestFixtures.TRAIN_GARAGE_A);
+
+ // AND: prepare request
+ RequestEntity request = RequestEntity
+ .delete(url(garage.getUuid()))
+ .build();
+ ParameterizedTypeReference responseType = new ParameterizedTypeReference<>() {
+ };
+
+ // WHEN:
+ ResponseEntity result = client.exchange(request, responseType);
+
+ // THEN:
+ assertThat("Correct response code is received", result.getStatusCode(), is(equalTo(HttpStatus.NO_CONTENT)));
+ Optional directoryAfter = trainGarageRepository.findById(garage.getUuid());
+ assertThat("Station directory is deleted", directoryAfter.isEmpty(), is(equalTo(true)));
+ }
+
+ @Test
+ @DisplayName("HTTP 404")
+ public void res404() {
+ // GIVEN: prepare data
+ trainGarageRepository.deleteAll();
+
+ // AND: prepare request
+ RequestEntity request = RequestEntity
+ .delete(url(UUID.randomUUID()))
+ .build();
+ ParameterizedTypeReference> responseType = new ParameterizedTypeReference<>() {
+ };
+
+ // WHEN:
+ ResponseEntity> result = client.exchange(request, responseType);
+
+ // THEN:
+ assertThat("Correct response code is received", result.getStatusCode(), is(equalTo(HttpStatus.NOT_FOUND)));
+ }
+
+}
diff --git a/src/test/java/org/fairdatatrain/trainhandler/acceptance/traingarage/Detail_GET.java b/src/test/java/org/fairdatatrain/trainhandler/acceptance/traingarage/Detail_GET.java
new file mode 100644
index 0000000..282ca55
--- /dev/null
+++ b/src/test/java/org/fairdatatrain/trainhandler/acceptance/traingarage/Detail_GET.java
@@ -0,0 +1,97 @@
+/**
+ * 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.trainhandler.acceptance.traingarage;
+
+import org.fairdatatrain.trainhandler.acceptance.WebIntegrationTest;
+import org.fairdatatrain.trainhandler.api.dto.traingarage.TrainGarageDTO;
+import org.fairdatatrain.trainhandler.data.model.TrainGarage;
+import org.fairdatatrain.trainhandler.data.repository.TrainGarageRepository;
+import org.junit.jupiter.api.DisplayName;
+import org.junit.jupiter.api.Test;
+import org.springframework.beans.factory.annotation.Autowired;
+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 java.util.UUID;
+
+import static java.lang.String.format;
+import static org.hamcrest.MatcherAssert.assertThat;
+import static org.hamcrest.Matchers.*;
+
+@DisplayName("GET /train-garages/:uuid")
+public class Detail_GET extends WebIntegrationTest {
+
+ private URI url(UUID uuid) {
+ return URI.create(format("/train-garages/%s", uuid));
+ }
+
+ @Autowired
+ private TrainGarageRepository trainGarageRepository;
+
+ @Test
+ @DisplayName("HTTP 200")
+ public void res200() {
+ // GIVEN: prepare data
+ trainGarageRepository.deleteAll();
+ TrainGarage garage = trainGarageRepository.save(TrainGarageTestFixtures.TRAIN_GARAGE_A);
+
+ // AND: prepare request
+ RequestEntity request = RequestEntity
+ .get(url(garage.getUuid()))
+ .build();
+ ParameterizedTypeReference responseType = new ParameterizedTypeReference<>() {
+ };
+
+ // WHEN:
+ ResponseEntity result = client.exchange(request, responseType);
+
+ // THEN:
+ assertThat("Correct response code is received", result.getStatusCode(), is(equalTo(HttpStatus.OK)));
+ assertThat("Body is not null", result.getBody(), is(notNullValue()));
+ Common.assertEquals(result.getBody(), garage);
+ }
+
+ @Test
+ @DisplayName("HTTP 404")
+ public void res404() {
+ // GIVEN: prepare data
+ trainGarageRepository.deleteAll();
+
+ // AND: prepare request
+ RequestEntity request = RequestEntity
+ .get(url(UUID.randomUUID()))
+ .build();
+ ParameterizedTypeReference> responseType = new ParameterizedTypeReference<>() {
+ };
+
+ // WHEN:
+ ResponseEntity> result = client.exchange(request, responseType);
+
+ // THEN:
+ assertThat("Correct response code is received", result.getStatusCode(), is(equalTo(HttpStatus.NOT_FOUND)));
+ }
+
+}
diff --git a/src/test/java/org/fairdatatrain/trainhandler/acceptance/traingarage/Detail_PUT.java b/src/test/java/org/fairdatatrain/trainhandler/acceptance/traingarage/Detail_PUT.java
new file mode 100644
index 0000000..71cd8b2
--- /dev/null
+++ b/src/test/java/org/fairdatatrain/trainhandler/acceptance/traingarage/Detail_PUT.java
@@ -0,0 +1,134 @@
+/**
+ * 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.trainhandler.acceptance.traingarage;
+
+import org.fairdatatrain.trainhandler.acceptance.WebIntegrationTest;
+import org.fairdatatrain.trainhandler.api.dto.traingarage.TrainGarageChangeDTO;
+import org.fairdatatrain.trainhandler.api.dto.traingarage.TrainGarageDTO;
+import org.fairdatatrain.trainhandler.data.model.TrainGarage;
+import org.fairdatatrain.trainhandler.data.repository.TrainGarageRepository;
+import org.junit.jupiter.api.DisplayName;
+import org.junit.jupiter.api.Test;
+import org.springframework.beans.factory.annotation.Autowired;
+import org.springframework.core.ParameterizedTypeReference;
+import org.springframework.http.HttpStatus;
+import org.springframework.http.MediaType;
+import org.springframework.http.RequestEntity;
+import org.springframework.http.ResponseEntity;
+
+import java.net.URI;
+import java.util.Optional;
+import java.util.UUID;
+
+import static java.lang.String.format;
+import static org.hamcrest.MatcherAssert.assertThat;
+import static org.hamcrest.Matchers.*;
+
+@DisplayName("PUT /train-garages/:uuid")
+public class Detail_PUT extends WebIntegrationTest {
+
+ private URI url(UUID uuid) {
+ return URI.create(format("/train-garages/%s", uuid));
+ }
+
+ @Autowired
+ private TrainGarageRepository trainGarageRepository;
+
+ @Test
+ @DisplayName("HTTP 200")
+ public void res200() {
+ // GIVEN: prepare data
+ trainGarageRepository.deleteAll();
+ TrainGarage garage = trainGarageRepository.save(TrainGarageTestFixtures.TRAIN_GARAGE_A);
+ TrainGarageChangeDTO reqDto = TrainGarageTestFixtures.TRAIN_GARAGE_A_UPDATE;
+
+ // AND: prepare request
+ RequestEntity request = RequestEntity
+ .put(url(garage.getUuid()))
+ .accept(MediaType.APPLICATION_JSON)
+ .body(reqDto);
+ ParameterizedTypeReference responseType = new ParameterizedTypeReference<>() {
+ };
+
+ // WHEN:
+ ResponseEntity result = client.exchange(request, responseType);
+
+ // THEN:
+ assertThat("Correct response code is received", result.getStatusCode(), is(equalTo(HttpStatus.OK)));
+ assertThat("Body is not null", result.getBody(), is(notNullValue()));
+ Common.assertEquals(result.getBody(), reqDto);
+ Optional directoryAfter = trainGarageRepository.findById(garage.getUuid());
+ assertThat("Station directory is saved", directoryAfter.isPresent(), is(equalTo(true)));
+ Common.assertEquals(result.getBody(), directoryAfter.get());
+ }
+
+ @Test
+ @DisplayName("HTTP 400")
+ public void res400() {
+ // GIVEN: prepare data
+ trainGarageRepository.deleteAll();
+ TrainGarage garage = trainGarageRepository.save(TrainGarageTestFixtures.TRAIN_GARAGE_A);
+ TrainGarageChangeDTO reqDto =
+ TrainGarageTestFixtures.TRAIN_GARAGE_A_UPDATE
+ .toBuilder()
+ .displayName("")
+ .build();
+
+ // AND: prepare request
+ RequestEntity request = RequestEntity
+ .put(url(garage.getUuid()))
+ .accept(MediaType.APPLICATION_JSON)
+ .body(reqDto);
+ ParameterizedTypeReference> responseType = new ParameterizedTypeReference<>() {
+ };
+
+ // WHEN:
+ ResponseEntity> result = client.exchange(request, responseType);
+
+ // THEN:
+ assertThat("Correct response code is received", result.getStatusCode(), is(equalTo(HttpStatus.BAD_REQUEST)));
+ }
+
+ @Test
+ @DisplayName("HTTP 404")
+ public void res404() {
+ // GIVEN: prepare data
+ trainGarageRepository.deleteAll();
+ TrainGarageChangeDTO reqDto = TrainGarageTestFixtures.TRAIN_GARAGE_A_UPDATE;
+
+ // AND: prepare request
+ RequestEntity request = RequestEntity
+ .put(url(UUID.randomUUID()))
+ .accept(MediaType.APPLICATION_JSON)
+ .body(reqDto);
+ ParameterizedTypeReference> responseType = new ParameterizedTypeReference<>() {
+ };
+
+ // WHEN:
+ ResponseEntity> result = client.exchange(request, responseType);
+
+ // THEN:
+ assertThat("Correct response code is received", result.getStatusCode(), is(equalTo(HttpStatus.NOT_FOUND)));
+ }
+
+}
diff --git a/src/test/java/org/fairdatatrain/trainhandler/acceptance/traingarage/List_GET.java b/src/test/java/org/fairdatatrain/trainhandler/acceptance/traingarage/List_GET.java
new file mode 100644
index 0000000..a7c0cb4
--- /dev/null
+++ b/src/test/java/org/fairdatatrain/trainhandler/acceptance/traingarage/List_GET.java
@@ -0,0 +1,142 @@
+/**
+ * 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.trainhandler.acceptance.traingarage;
+
+import org.fairdatatrain.trainhandler.acceptance.WebIntegrationTest;
+import org.fairdatatrain.trainhandler.acceptance.helper.HelperPage;
+import org.fairdatatrain.trainhandler.api.dto.traingarage.TrainGarageDTO;
+import org.fairdatatrain.trainhandler.data.model.TrainGarage;
+import org.fairdatatrain.trainhandler.data.repository.TrainGarageRepository;
+import org.junit.jupiter.api.DisplayName;
+import org.junit.jupiter.api.Test;
+import org.springframework.beans.factory.annotation.Autowired;
+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 java.util.List;
+import java.util.stream.StreamSupport;
+
+import static java.lang.String.format;
+import static org.hamcrest.MatcherAssert.assertThat;
+import static org.hamcrest.Matchers.*;
+
+@DisplayName("GET /train-garages")
+public class List_GET extends WebIntegrationTest {
+
+ private URI url() {
+ return URI.create("/train-garages");
+ }
+
+ private URI urlPaged(int page, int size) {
+ return URI.create(format("/train-garages?page=%d&size=%d", page, size));
+ }
+
+ @Autowired
+ private TrainGarageRepository trainGarageRepository;
+
+ @Test
+ @DisplayName("HTTP 200: empty")
+ public void res200_empty() {
+ // GIVEN: prepare data
+ trainGarageRepository.deleteAll();
+
+ // AND: prepare request
+ RequestEntity