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